+ support Movie disk folders in movie mode

+ improved handling for derivate files (files with the same name but different extensions) in movie mode
This commit is contained in:
Reinhard Pointner 2012-02-10 16:43:09 +00:00
parent 67fe97c345
commit cc5845b2a0
19 changed files with 290 additions and 209 deletions

View File

@ -109,8 +109,8 @@
<include name="org/slf4j/**" />
</zipfileset>
<zipfileset src="${dir.lib}/guava.jar">
<include name="com/google/common/**" />
<zipfileset src="${dir.lib}/commons-io.jar">
<include name="org/apache/commons/io/**" />
</zipfileset>
<zipfileset src="${dir.lib}/jna.jar">

View File

@ -49,7 +49,7 @@
<jar href="mediainfo.jar" download="lazy" part="native" />
<jar href="nekohtml.jar" download="lazy" part="scraper" />
<jar href="xercesImpl.jar" download="lazy" part="scraper" />
<jar href="guava.jar" download="lazy" />
<jar href="commons-io.jar" download="lazy" />
<jar href="junrar-custom.jar" download="lazy" />
</resources>

BIN
lib/commons-io.jar Normal file

Binary file not shown.

Binary file not shown.

View File

@ -3,7 +3,6 @@ package net.sourceforge.filebot.cli;
import static java.lang.String.*;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.filebot.WebServices.*;
@ -47,6 +46,7 @@ import net.sourceforge.filebot.format.MediaBindingBean;
import net.sourceforge.filebot.hash.HashType;
import net.sourceforge.filebot.hash.VerificationFileReader;
import net.sourceforge.filebot.hash.VerificationFileWriter;
import net.sourceforge.filebot.media.ReleaseInfo;
import net.sourceforge.filebot.similarity.EpisodeMetrics;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.similarity.Matcher;
@ -260,49 +260,68 @@ public class CmdlineOperations implements CmdlineInterface {
}
public List<File> renameMovie(Collection<File> mediaFiles, String query, ExpressionFormat format, MovieIdentificationService service, Locale locale, boolean strict) throws Exception {
public List<File> renameMovie(Collection<File> files, String query, ExpressionFormat format, MovieIdentificationService service, Locale locale, boolean strict) throws Exception {
CLILogger.config(format("Rename movies using [%s]", service.getName()));
// handle movie files
File[] movieFiles = filter(mediaFiles, VIDEO_FILES).toArray(new File[0]);
File[] subtitleFiles = filter(mediaFiles, SUBTITLE_FILES).toArray(new File[0]);
Movie[] movieByFileHash = new Movie[movieFiles.length];
List<File> movieFiles = filter(files, VIDEO_FILES);
if (movieFiles.length > 0 && query == null) {
// match movie hashes online
Map<File, List<File>> derivatesByMovieFile = new HashMap<File, List<File>>();
for (File movieFile : movieFiles) {
derivatesByMovieFile.put(movieFile, new ArrayList<File>());
}
for (File file : files) {
for (File movieFile : movieFiles) {
if (!file.equals(movieFile) && isDerived(file, movieFile)) {
derivatesByMovieFile.get(movieFile).add(file);
break;
}
}
}
List<File> standaloneFiles = new ArrayList<File>(files);
for (List<File> derivates : derivatesByMovieFile.values()) {
standaloneFiles.removeAll(derivates);
}
List<File> movieMatchFiles = new ArrayList<File>();
movieMatchFiles.addAll(movieFiles);
movieMatchFiles.addAll(filter(files, new ReleaseInfo().getDiskFolderFilter()));
movieMatchFiles.addAll(filter(standaloneFiles, SUBTITLE_FILES));
// map movies to (possibly multiple) files (in natural order)
Map<Movie, SortedSet<File>> filesByMovie = new HashMap<Movie, SortedSet<File>>();
// match movie hashes online
Map<File, Movie> movieByFile = new HashMap<File, Movie>();
if (query == null && movieFiles.size() > 0) {
try {
CLILogger.fine(format("Looking up movie by filehash via [%s]", service.getName()));
movieByFileHash = service.getMovieDescriptors(movieFiles, locale);
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", movieByFileHash.length - frequency(asList(movieByFileHash), null)); // number of positive hash lookups
Map<File, Movie> hashLookup = service.getMovieDescriptors(movieFiles, locale);
movieByFile.putAll(hashLookup);
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", hashLookup.size()); // number of positive hash lookups
} catch (UnsupportedOperationException e) {
CLILogger.fine(format("%s: Hash lookup not supported", service.getName()));
}
}
if (subtitleFiles.length > 0 && movieFiles.length == 0) {
// special handling if there is only subtitle files
movieByFileHash = new Movie[subtitleFiles.length];
movieFiles = subtitleFiles;
subtitleFiles = new File[0];
}
if (query != null) {
CLILogger.fine(format("Looking up movie by query [%s]", query));
Movie result = (Movie) selectSearchResult(query, service.searchMovie(query, locale), strict).get(0);
fill(movieByFileHash, result);
// force all mappings
for (File file : movieMatchFiles) {
movieByFile.put(file, result);
}
}
// map movies to (possibly multiple) files (in natural order)
Map<Movie, SortedSet<File>> filesByMovie = new HashMap<Movie, SortedSet<File>>();
// map all files by movie
for (int i = 0; i < movieFiles.length; i++) {
Movie movie = movieByFileHash[i];
for (File file : movieMatchFiles) {
Movie movie = movieByFile.get(file);
// unknown hash, try via imdb id from nfo file
if (movie == null) {
CLILogger.fine(format("Auto-detect movie from context: [%s]", movieFiles[i]));
Collection<Movie> results = detectMovie(movieFiles[i], null, service, locale, strict);
CLILogger.fine(format("Auto-detect movie from context: [%s]", file));
Collection<Movie> results = detectMovie(file, null, service, locale, strict);
movie = (Movie) selectSearchResult(query, results, strict).get(0);
if (movie != null) {
@ -320,7 +339,7 @@ public class CmdlineOperations implements CmdlineInterface {
filesByMovie.put(movie, movieParts);
}
movieParts.add(movieFiles[i]);
movieParts.add(file);
}
}
@ -341,17 +360,13 @@ public class CmdlineOperations implements CmdlineInterface {
}
matches.add(new Match<File, Movie>(file, part));
}
}
// handle subtitle files
for (File subtitle : subtitleFiles) {
// check if subtitle corresponds to a movie file (same name, different extension)
for (Match<File, ?> movieMatch : matches) {
if (isDerived(subtitle, movieMatch.getValue())) {
matches.add(new Match<File, Object>(subtitle, movieMatch.getCandidate()));
// movie match found, we're done
break;
// automatically add matches for derivates
List<File> derivates = derivatesByMovieFile.get(file);
if (derivates != null) {
for (File derivate : derivates) {
matches.add(new Match<File, Movie>(derivate, part));
}
}
}
}
@ -387,7 +402,7 @@ public class CmdlineOperations implements CmdlineInterface {
for (Entry<File, File> it : renameMap.entrySet()) {
try {
// rename file, throw exception on failure
File destination = renameFile(it.getKey(), it.getValue());
File destination = moveRename(it.getKey(), it.getValue());
CLILogger.info(format("Renamed [%s] to [%s]", it.getKey(), it.getValue()));
// remember successfully renamed matches for history entry and possible revert

View File

@ -37,7 +37,7 @@ File.metaClass.hasExtension = { String... ext -> hasExtension(delegate, ext) }
File.metaClass.isDerived = { f -> isDerived(delegate, f) }
File.metaClass.validateFileName = { validateFileName(delegate) }
File.metaClass.validateFilePath = { validateFilePath(delegate) }
File.metaClass.moveTo = { f -> renameFile(delegate, f) }
File.metaClass.moveTo = { f -> moveRename(delegate, f instanceof File ? f : new File(f.toString())) }
List.metaClass.mapByFolder = { mapByFolder(delegate) }
List.metaClass.mapByExtension = { mapByExtension(delegate) }
String.metaClass.getExtension = { getExtension(delegate) }

View File

@ -8,6 +8,7 @@ import static net.sourceforge.filebot.similarity.Normalization.*;
import static net.sourceforge.tuned.FileUtilities.*;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
@ -49,6 +50,11 @@ public class MediaDetection {
private static final ReleaseInfo releaseInfo = new ReleaseInfo();
public static boolean isDiskFolder(File folder) {
return releaseInfo.getDiskFolderFilter().accept(folder);
}
public static Map<Set<File>, Set<String>> mapSeriesNamesByFiles(Collection<File> files, Locale locale) throws Exception {
SortedMap<File, List<File>> filesByFolder = mapByFolder(filter(files, VIDEO_FILES, SUBTITLE_FILES));
@ -156,8 +162,8 @@ public class MediaDetection {
Set<Movie> options = new LinkedHashSet<Movie>();
// lookup by file hash
if (hashLookupService != null) {
for (Movie movie : hashLookupService.getMovieDescriptors(new File[] { movieFile }, locale)) {
if (hashLookupService != null && movieFile.isFile()) {
for (Movie movie : hashLookupService.getMovieDescriptors(singleton(movieFile), locale).values()) {
if (movie != null) {
options.add(movie);
}

View File

@ -2,13 +2,13 @@
package net.sourceforge.filebot.media;
import static java.util.Arrays.*;
import static java.util.ResourceBundle.*;
import static java.util.regex.Pattern.*;
import static net.sourceforge.filebot.similarity.Normalization.*;
import static net.sourceforge.tuned.StringUtilities.*;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
@ -125,7 +125,7 @@ public class ReleaseInfo {
languageMap.put(locale.getISO3Language(), locale);
// map display language names for given locales
for (Locale language : asList(supportedLanguageName)) {
for (Locale language : supportedLanguageName) {
languageMap.put(locale.getDisplayLanguage(language), locale);
}
}
@ -174,6 +174,11 @@ public class ReleaseInfo {
}
public FileFilter getDiskFolderFilter() {
return new FolderEntryFilter(compile(getBundle(getClass().getName()).getString("pattern.diskfolder.entry")));
}
// fetch release group names online and try to update the data every other day
protected final CachedResource<String[]> releaseGroupResource = new PatternResource(getBundle(getClass().getName()).getString("url.release-groups"));
protected final CachedResource<String[]> queryBlacklistResource = new PatternResource(getBundle(getClass().getName()).getString("url.query-blacklist"));
@ -217,4 +222,28 @@ public class ReleaseInfo {
}
}
protected static class FolderEntryFilter implements FileFilter {
private final Pattern entryPattern;
public FolderEntryFilter(Pattern entryPattern) {
this.entryPattern = entryPattern;
}
@Override
public boolean accept(File dir) {
if (dir.isDirectory()) {
for (String entry : dir.list()) {
if (entryPattern.matcher(entry).matches()) {
return true;
}
}
}
return false;
}
}
}

View File

@ -12,3 +12,6 @@ url.query-blacklist: http://filebot.sourceforge.net/data/query-blacklist.txt
# list of all movies (id, name, year)
url.movie-list: http://filebot.sourceforge.net/data/movies.txt.gz
# disk folder matcher
pattern.diskfolder.entry: ^BDMV$|^HVDVD_TS$|^VIDEO_TS$|^AUDIO_TS$|^VCD$

View File

@ -2,11 +2,14 @@
package net.sourceforge.filebot.ui.rename;
import static net.sourceforge.tuned.FileUtilities.*;
import static java.util.Arrays.*;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import net.sourceforge.filebot.media.MediaDetection;
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
import net.sourceforge.tuned.FastFile;
@ -15,30 +18,46 @@ class FilesListTransferablePolicy extends FileTransferablePolicy {
private final List<File> model;
public FilesListTransferablePolicy(List<File> model) {
this.model = model;
}
@Override
protected boolean accept(List<File> files) {
return true;
}
@Override
protected void clear() {
model.clear();
}
@Override
protected void load(List<File> files) {
model.addAll(FastFile.foreach(flatten(files, 5, false)));
List<File> entries = new ArrayList<File>();
LinkedList<File> queue = new LinkedList<File>(files);
while (queue.size() > 0) {
File f = queue.removeFirst();
if (f.isHidden())
continue;
if (f.isFile() || MediaDetection.isDiskFolder(f)) {
entries.add(f);
} else {
queue.addAll(0, asList(f.listFiles()));
}
}
model.addAll(FastFile.foreach(entries));
}
@Override
public String getFileFilterDescription() {
return "files and folders";

View File

@ -27,8 +27,8 @@ import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
@ -50,8 +50,8 @@ import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.RowFilter;
import javax.swing.SortOrder;
import javax.swing.RowSorter.SortKey;
import javax.swing.SortOrder;
import javax.swing.border.CompoundBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.DocumentEvent;
@ -94,7 +94,7 @@ class HistoryDialog extends JDialog {
private final JTable elementTable = createTable(elementModel);
public HistoryDialog(Window owner) {
super(owner, "Rename History", ModalityType.DOCUMENT_MODAL);
@ -179,7 +179,7 @@ class HistoryDialog extends JDialog {
private final DateFormat format = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT);
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
return super.getTableCellRendererComponent(table, format.format(value), isSelected, hasFocus, row, column);
@ -244,7 +244,7 @@ class HistoryDialog extends JDialog {
setSize(580, 640);
}
public void setModel(History history) {
// update table model
sequenceModel.setData(history.sequences());
@ -261,17 +261,17 @@ class HistoryDialog extends JDialog {
initializeInfoLabel();
}
public History getModel() {
return new History(sequenceModel.getData());
}
public JLabel getInfoLabel() {
return infoLabel;
}
private void initializeInfoLabel() {
int count = 0;
Date since = new Date();
@ -286,7 +286,7 @@ class HistoryDialog extends JDialog {
infoLabel.setText(String.format("A total of %,d files have been renamed since %s.", count, DateFormat.getDateInstance().format(since)));
}
private JScrollPane createScrollPaneGroup(String title, JComponent component) {
JScrollPane scrollPane = new JScrollPane(component);
scrollPane.setBorder(new CompoundBorder(new TitledBorder(title), scrollPane.getBorder()));
@ -294,7 +294,7 @@ class HistoryDialog extends JDialog {
return scrollPane;
}
private JTable createTable(TableModel model) {
JTable table = new JTable(model);
table.setBackground(Color.white);
@ -312,7 +312,7 @@ class HistoryDialog extends JDialog {
return table;
}
private final Action closeAction = new AbstractAction("Close") {
@Override
@ -337,13 +337,13 @@ class HistoryDialog extends JDialog {
maybeShowPopup(e);
}
@Override
public void mouseReleased(MouseEvent e) {
maybeShowPopup(e);
}
private void maybeShowPopup(MouseEvent e) {
if (e.isPopupTrigger()) {
JTable table = (JTable) e.getSource();
@ -383,30 +383,30 @@ class HistoryDialog extends JDialog {
}
};
private static class RevertAction extends AbstractAction {
public static final String ELEMENTS = "elements";
public static final String PARENT = "parent";
public RevertAction(Collection<Element> elements, HistoryDialog parent) {
putValue(NAME, "Revert...");
putValue(ELEMENTS, elements.toArray(new Element[0]));
putValue(PARENT, parent);
}
public Element[] elements() {
return (Element[]) getValue(ELEMENTS);
}
public HistoryDialog parent() {
return (HistoryDialog) getValue(PARENT);
}
private enum Option {
Rename {
@ -431,7 +431,7 @@ class HistoryDialog extends JDialog {
}
}
@Override
public void actionPerformed(ActionEvent e) {
// use default directory
@ -501,13 +501,13 @@ class HistoryDialog extends JDialog {
}
}
private void rename(File directory) {
int count = 0;
for (Entry<File, File> entry : getRenameMap(directory).entrySet()) {
try {
renameFile(entry.getKey(), entry.getValue());
moveRename(entry.getKey(), entry.getValue());
count++;
} catch (Exception e) {
Logger.getLogger(HistoryDialog.class.getName()).log(Level.SEVERE, e.getMessage(), e);
@ -528,7 +528,7 @@ class HistoryDialog extends JDialog {
parent().repaint();
}
private Map<File, File> getRenameMap(File directory) {
Map<File, File> renameMap = new LinkedHashMap<File, File>();
@ -552,7 +552,7 @@ class HistoryDialog extends JDialog {
return renameMap;
}
private List<File> getMissingFiles(File directory) {
List<File> missingFiles = new ArrayList<File>();
@ -565,7 +565,7 @@ class HistoryDialog extends JDialog {
}
}
private final FileTransferablePolicy importHandler = new FileTransferablePolicy() {
@Override
@ -573,13 +573,13 @@ class HistoryDialog extends JDialog {
return FileUtilities.containsOnly(files, new ExtensionFileFilter("xml"));
}
@Override
protected void clear() {
setModel(new History());
}
@Override
protected void load(List<File> files) throws IOException {
History history = getModel();
@ -594,7 +594,7 @@ class HistoryDialog extends JDialog {
}
}
@Override
public String getFileFilterDescription() {
return "history files (.xml)";
@ -609,30 +609,30 @@ class HistoryDialog extends JDialog {
return true;
}
@Override
public void export(File file) throws IOException {
History.exportHistory(getModel(), file);
}
@Override
public String getDefaultFileName() {
return "history.xml";
}
};
private static class HistoryFilter extends RowFilter<Object, Integer> {
private final Pattern filter;
public HistoryFilter(String filter) {
this.filter = compile(quote(filter), CASE_INSENSITIVE | UNICODE_CASE | CANON_EQ);
}
@Override
public boolean include(Entry<?, ? extends Integer> entry) {
// sequence model
@ -658,23 +658,23 @@ class HistoryDialog extends JDialog {
throw new IllegalArgumentException("Illegal model: " + entry.getModel());
}
private boolean include(Element element) {
return include(element.to()) || include(element.from()) || include(element.dir().getPath());
}
private boolean include(String value) {
return filter.matcher(value).find();
}
}
private static class SequenceTableModel extends AbstractTableModel {
private List<Sequence> data = emptyList();
public void setData(List<Sequence> data) {
this.data = new ArrayList<Sequence>(data);
@ -682,12 +682,12 @@ class HistoryDialog extends JDialog {
fireTableDataChanged();
}
public List<Sequence> getData() {
return unmodifiableList(data);
}
@Override
public String getColumnName(int column) {
switch (column) {
@ -704,19 +704,19 @@ class HistoryDialog extends JDialog {
}
}
@Override
public int getColumnCount() {
return 4;
}
@Override
public int getRowCount() {
return data.size();
}
@Override
public Class<?> getColumnClass(int column) {
switch (column) {
@ -733,7 +733,7 @@ class HistoryDialog extends JDialog {
}
}
@Override
public Object getValueAt(int row, int column) {
switch (column) {
@ -750,12 +750,12 @@ class HistoryDialog extends JDialog {
}
}
public Sequence getRow(int row) {
return data.get(row);
}
private String getName(Sequence sequence) {
StringBuilder sb = new StringBuilder();
@ -775,12 +775,12 @@ class HistoryDialog extends JDialog {
}
}
private static class ElementTableModel extends AbstractTableModel {
private List<Element> data = emptyList();
public void setData(List<Element> data) {
this.data = new ArrayList<Element>(data);
@ -788,7 +788,7 @@ class HistoryDialog extends JDialog {
fireTableDataChanged();
}
@Override
public String getColumnName(int column) {
switch (column) {
@ -805,19 +805,19 @@ class HistoryDialog extends JDialog {
}
}
@Override
public int getColumnCount() {
return 4;
}
@Override
public int getRowCount() {
return data.size();
}
@Override
public Class<?> getColumnClass(int column) {
switch (column) {
@ -834,7 +834,7 @@ class HistoryDialog extends JDialog {
}
}
@Override
public Object getValueAt(int row, int column) {
switch (column) {
@ -851,12 +851,12 @@ class HistoryDialog extends JDialog {
}
}
public Element getRow(int row) {
return data.get(row);
}
public boolean isBroken(int row) {
Element element = data.get(row);

View File

@ -2,8 +2,6 @@
package net.sourceforge.filebot.ui.rename;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.filebot.media.MediaDetection.*;
import static net.sourceforge.tuned.FileUtilities.*;
@ -37,6 +35,7 @@ import javax.swing.Action;
import javax.swing.SwingUtilities;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.media.ReleaseInfo;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
import net.sourceforge.filebot.similarity.SimilarityMetric;
@ -59,24 +58,30 @@ class MovieHashMatcher implements AutoCompleteMatcher {
@Override
public List<Match<File, ?>> match(final List<File> files, final Locale locale, final boolean autodetect, final Component parent) throws Exception {
// handle movie files
File[] movieFiles = filter(files, VIDEO_FILES).toArray(new File[0]);
File[] subtitleFiles = filter(files, SUBTITLE_FILES).toArray(new File[0]);
Movie[] movieByFileHash = null;
List<File> movieFiles = filter(files, VIDEO_FILES);
if (movieFiles.length > 0) {
// match movie hashes online
try {
movieByFileHash = service.getMovieDescriptors(movieFiles, locale);
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", movieByFileHash.length - frequency(asList(movieByFileHash), null)); // number of positive hash lookups
} catch (UnsupportedOperationException e) {
movieByFileHash = new Movie[movieFiles.length];
}
} else if (subtitleFiles.length > 0) {
// special handling if there is only subtitle files
movieByFileHash = new Movie[subtitleFiles.length];
movieFiles = subtitleFiles;
subtitleFiles = new File[0];
Map<File, List<File>> derivatesByMovieFile = new HashMap<File, List<File>>();
for (File movieFile : movieFiles) {
derivatesByMovieFile.put(movieFile, new ArrayList<File>());
}
for (File file : files) {
for (File movieFile : movieFiles) {
if (!file.equals(movieFile) && isDerived(file, movieFile)) {
derivatesByMovieFile.get(movieFile).add(file);
break;
}
}
}
List<File> standaloneFiles = new ArrayList<File>(files);
for (List<File> derivates : derivatesByMovieFile.values()) {
standaloneFiles.removeAll(derivates);
}
List<File> movieMatchFiles = new ArrayList<File>();
movieMatchFiles.addAll(movieFiles);
movieMatchFiles.addAll(filter(files, new ReleaseInfo().getDiskFolderFilter()));
movieMatchFiles.addAll(filter(standaloneFiles, SUBTITLE_FILES));
// map movies to (possibly multiple) files (in natural order)
Map<Movie, SortedSet<File>> filesByMovie = new HashMap<Movie, SortedSet<File>>();
@ -84,10 +89,21 @@ class MovieHashMatcher implements AutoCompleteMatcher {
// match remaining movies file by file in parallel
List<Callable<Entry<File, Movie>>> grabMovieJobs = new ArrayList<Callable<Entry<File, Movie>>>();
// match movie hashes online
Map<File, Movie> movieByFile = new HashMap<File, Movie>();
if (movieFiles.size() > 0) {
try {
Map<File, Movie> hashLookup = service.getMovieDescriptors(movieFiles, locale);
movieByFile.putAll(hashLookup);
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", hashLookup.size()); // number of positive hash lookups
} catch (UnsupportedOperationException e) {
// ignore
}
}
// map all files by movie
for (int i = 0; i < movieFiles.length; i++) {
final Movie movie = movieByFileHash[i];
final File file = movieFiles[i];
for (final File file : movieMatchFiles) {
final Movie movie = movieByFile.get(file);
grabMovieJobs.add(new Callable<Entry<File, Movie>>() {
@Override
@ -146,17 +162,13 @@ class MovieHashMatcher implements AutoCompleteMatcher {
}
matches.add(new Match<File, Movie>(file, part));
}
}
// handle subtitle files
for (File subtitle : subtitleFiles) {
// check if subtitle corresponds to a movie file (same name, different extension)
for (Match<File, ?> movieMatch : matches) {
if (isDerived(subtitle, movieMatch.getValue())) {
matches.add(new Match<File, Object>(subtitle, movieMatch.getCandidate()));
// movie match found, we're done
break;
// automatically add matches for derivates
List<File> derivates = derivatesByMovieFile.get(file);
if (derivates != null) {
for (File derivate : derivates) {
matches.add(new Match<File, Movie>(derivate, part));
}
}
}
}

View File

@ -13,16 +13,16 @@ import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.io.File;
import java.util.AbstractList;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;
@ -34,15 +34,15 @@ import javax.swing.SwingWorker;
import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.tuned.ui.ProgressDialog;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
import net.sourceforge.tuned.ui.ProgressDialog.Cancellable;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
class RenameAction extends AbstractAction {
private final RenameModel model;
public RenameAction(RenameModel model) {
this.model = model;
@ -51,7 +51,7 @@ class RenameAction extends AbstractAction {
putValue(SHORT_DESCRIPTION, "Rename files");
}
public void actionPerformed(ActionEvent evt) {
if (model.getRenameMap().isEmpty()) {
return;
@ -85,7 +85,7 @@ class RenameAction extends AbstractAction {
window.setCursor(Cursor.getDefaultCursor());
}
private Map<File, File> checkRenamePlan(List<Entry<File, File>> renamePlan) {
// build rename map and perform some sanity checks
Map<File, File> renameMap = new HashMap<File, File>();
@ -117,7 +117,7 @@ class RenameAction extends AbstractAction {
return renameMap;
}
private List<Entry<File, File>> validate(Map<File, String> renameMap, Window parent) {
final List<Entry<File, File>> source = new ArrayList<Entry<File, File>>(renameMap.size());
@ -132,13 +132,13 @@ class RenameAction extends AbstractAction {
return source.get(index).getValue();
}
@Override
public File set(int index, File name) {
return source.get(index).setValue(name);
}
@Override
public int size() {
return source.size();
@ -154,7 +154,7 @@ class RenameAction extends AbstractAction {
return emptyList();
}
protected ProgressDialog createProgressDialog(Window parent, final RenameJob job) {
final ProgressDialog dialog = new ProgressDialog(parent, job);
@ -175,7 +175,7 @@ class RenameAction extends AbstractAction {
}
}
@Override
protected void done(PropertyChangeEvent evt) {
dialog.close();
@ -185,19 +185,19 @@ class RenameAction extends AbstractAction {
return dialog;
}
protected class RenameJob extends SwingWorker<Map<File, File>, Void> implements Cancellable {
private final Map<File, File> renameMap;
private final Map<File, File> renameLog;
public RenameJob(Map<File, File> renameMap) {
this.renameMap = synchronizedMap(renameMap);
this.renameLog = synchronizedMap(new LinkedHashMap<File, File>());
}
@Override
protected Map<File, File> doInBackground() throws Exception {
for (Entry<File, File> mapping : renameMap.entrySet()) {
@ -209,7 +209,7 @@ class RenameAction extends AbstractAction {
firePropertyChange("currentFile", mapping.getKey(), mapping.getValue());
// rename file, throw exception on failure
renameFile(mapping.getKey(), mapping.getValue());
moveRename(mapping.getKey(), mapping.getValue());
// remember successfully renamed matches for history entry and possible revert
renameLog.put(mapping.getKey(), mapping.getValue());
@ -218,7 +218,7 @@ class RenameAction extends AbstractAction {
return renameLog;
}
@Override
protected void done() {
try {
@ -251,7 +251,7 @@ class RenameAction extends AbstractAction {
}
}
@Override
public boolean cancel() {
return cancel(true);

View File

@ -10,8 +10,10 @@ import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -122,7 +124,7 @@ public class IMDbClient implements MovieIdentificationService {
@Override
public Movie[] getMovieDescriptors(File[] movieFiles, Locale locale) throws Exception {
public Map<File, Movie> getMovieDescriptors(Collection<File> movieFiles, Locale locale) throws Exception {
throw new UnsupportedOperationException();
}

View File

@ -3,8 +3,10 @@ package net.sourceforge.filebot.web;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.swing.Icon;
@ -13,16 +15,16 @@ public interface MovieIdentificationService {
public String getName();
public Icon getIcon();
public List<Movie> searchMovie(String query, Locale locale) throws Exception;
public Movie getMovieDescriptor(int imdbid, Locale locale) throws Exception;
public Movie[] getMovieDescriptors(File[] movieFiles, Locale locale) throws Exception;
public Map<File, Movie> getMovieDescriptors(Collection<File> movieFiles, Locale locale) throws Exception;
}

View File

@ -4,6 +4,7 @@ package net.sourceforge.filebot.web;
import static java.lang.Math.*;
import static java.util.Arrays.*;
import static java.util.Collections.*;
import static net.sourceforge.filebot.web.OpenSubtitlesHasher.*;
import java.io.File;
@ -12,6 +13,7 @@ import java.net.URI;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@ -184,37 +186,36 @@ public class OpenSubtitlesClient implements SubtitleProvider, VideoHashSubtitleS
public Movie getMovieDescriptor(File movieFile, Locale locale) throws Exception {
return getMovieDescriptors(new File[] { movieFile }, locale)[0];
return getMovieDescriptors(singleton(movieFile), locale).get(movieFile);
}
@Override
public Movie[] getMovieDescriptors(File[] movieFiles, Locale locale) throws Exception {
public Map<File, Movie> getMovieDescriptors(Collection<File> movieFiles, Locale locale) throws Exception {
// create result array
Movie[] result = new Movie[movieFiles.length];
Map<File, Movie> result = new HashMap<File, Movie>();
// compute movie hashes
Map<String, Integer> indexMap = new HashMap<String, Integer>(movieFiles.length);
Map<String, File> hashMap = new HashMap<String, File>(movieFiles.size());
for (int i = 0; i < movieFiles.length; i++) {
if (movieFiles[i].length() > HASH_CHUNK_SIZE) {
indexMap.put(computeHash(movieFiles[i]), i); // remember original index
for (File file : movieFiles) {
if (file.length() > HASH_CHUNK_SIZE) {
hashMap.put(computeHash(file), file); // map file by hash
}
}
if (indexMap.size() > 0) {
if (hashMap.size() > 0) {
// require login
login();
// dispatch query for all hashes
List<String> hashes = new ArrayList<String>(indexMap.keySet());
List<String> hashes = new ArrayList<String>(hashMap.keySet());
int batchSize = 50;
for (int bn = 0; bn < ceil((float) hashes.size() / batchSize); bn++) {
List<String> batch = hashes.subList(bn * batchSize, min((bn * batchSize) + batchSize, hashes.size()));
for (Entry<String, Movie> entry : xmlrpc.checkMovieHash(batch).entrySet()) {
int index = indexMap.get(entry.getKey());
result[index] = entry.getValue();
result.put(hashMap.get(entry.getKey()), entry.getValue());
}
}
}

View File

@ -13,6 +13,7 @@ import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
@ -70,11 +71,6 @@ public class TMDbClient implements MovieIdentificationService {
}
public List<Movie> searchMovie(File file, Locale locale) throws IOException, SAXException {
throw new UnsupportedOperationException();
}
public List<Movie> searchMovie(String hash, long bytesize, Locale locale) throws IOException, SAXException {
return getMovies("Media.getInfo", hash + "/" + bytesize, locale);
}
@ -96,17 +92,8 @@ public class TMDbClient implements MovieIdentificationService {
@Override
public Movie[] getMovieDescriptors(File[] movieFiles, Locale locale) throws Exception {
Movie[] movies = new Movie[movieFiles.length];
for (int i = 0; i < movies.length; i++) {
List<Movie> options = searchMovie(movieFiles[i], locale);
// just use first result, if possible
movies[i] = options.isEmpty() ? null : options.get(0);
}
return movies;
public Map<File, Movie> getMovieDescriptors(Collection<File> movieFiles, Locale locale) throws Exception {
throw new UnsupportedOperationException();
}

View File

@ -13,43 +13,36 @@ public class FastFile extends File {
private Long length;
private Boolean isDirectory;
private Boolean isFile;
private Boolean exists;
public FastFile(String path) {
super(path);
}
public FastFile(File parent, String child) {
super(parent, child);
}
@Override
public long length() {
return length != null ? length : (length = super.length());
}
@Override
public boolean exists() {
return exists != null ? exists : (exists = super.exists());
}
@Override
public boolean isDirectory() {
return isDirectory != null ? isDirectory : (isDirectory = super.isDirectory());
}
@Override
public boolean isFile() {
return isFile != null ? isFile : (isFile = super.isFile());
}
@Override
public File[] listFiles() {
String[] names = list();
@ -62,12 +55,12 @@ public class FastFile extends File {
return files;
}
public static List<FastFile> foreach(File... files) {
return foreach(Arrays.asList(files));
}
public static List<FastFile> foreach(final List<File> files) {
List<FastFile> result = new ArrayList<FastFile>(files.size());

View File

@ -42,7 +42,7 @@ import com.ibm.icu.text.CharsetMatch;
public final class FileUtilities {
public static File renameFile(File source, File destination) throws IOException {
public static File moveRename(File source, File destination) throws IOException {
// resolve destination
if (!destination.isAbsolute()) {
// same folder, different name
@ -57,25 +57,37 @@ public final class FileUtilities {
throw new IOException("Failed to create folder: " + destinationFolder);
}
try {
renameFileNIO2(source, destination);
} catch (LinkageError e) {
renameFileIO(source, destination);
if (source.isDirectory()) { // move folder
moveFolderIO(source, destination);
} else { // move file
try {
moveFileNIO2(source, destination);
} catch (LinkageError e) {
moveFileIO(source, destination);
}
}
return destination;
}
private static void renameFileNIO2(File source, File destination) throws IOException {
private static void moveFileNIO2(File source, File destination) throws IOException {
java.nio.file.Files.move(source.toPath(), destination.toPath());
}
private static void renameFileIO(File source, File destination) throws IOException {
private static void moveFileIO(File source, File destination) throws IOException {
if (!source.renameTo(destination)) {
// try using Guava IO utilities, that'll just copy files if renameTo() fails
com.google.common.io.Files.move(source, destination);
// use "copy and delete" as fallback if standard rename fails
org.apache.commons.io.FileUtils.moveFile(source, destination);
}
}
private static void moveFolderIO(File source, File destination) throws IOException {
if (!source.renameTo(destination)) {
// use "copy and delete" as fallback if standard move/rename fails
org.apache.commons.io.FileUtils.moveDirectory(source, destination);
}
}