+ 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/**" /> <include name="org/slf4j/**" />
</zipfileset> </zipfileset>
<zipfileset src="${dir.lib}/guava.jar"> <zipfileset src="${dir.lib}/commons-io.jar">
<include name="com/google/common/**" /> <include name="org/apache/commons/io/**" />
</zipfileset> </zipfileset>
<zipfileset src="${dir.lib}/jna.jar"> <zipfileset src="${dir.lib}/jna.jar">

View File

@ -49,7 +49,7 @@
<jar href="mediainfo.jar" download="lazy" part="native" /> <jar href="mediainfo.jar" download="lazy" part="native" />
<jar href="nekohtml.jar" download="lazy" part="scraper" /> <jar href="nekohtml.jar" download="lazy" part="scraper" />
<jar href="xercesImpl.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" /> <jar href="junrar-custom.jar" download="lazy" />
</resources> </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.lang.String.*;
import static java.util.Arrays.*;
import static java.util.Collections.*; import static java.util.Collections.*;
import static net.sourceforge.filebot.MediaTypes.*; import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.filebot.WebServices.*; 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.HashType;
import net.sourceforge.filebot.hash.VerificationFileReader; import net.sourceforge.filebot.hash.VerificationFileReader;
import net.sourceforge.filebot.hash.VerificationFileWriter; import net.sourceforge.filebot.hash.VerificationFileWriter;
import net.sourceforge.filebot.media.ReleaseInfo;
import net.sourceforge.filebot.similarity.EpisodeMetrics; import net.sourceforge.filebot.similarity.EpisodeMetrics;
import net.sourceforge.filebot.similarity.Match; import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.similarity.Matcher; 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())); CLILogger.config(format("Rename movies using [%s]", service.getName()));
// handle movie files // handle movie files
File[] movieFiles = filter(mediaFiles, VIDEO_FILES).toArray(new File[0]); List<File> movieFiles = filter(files, VIDEO_FILES);
File[] subtitleFiles = filter(mediaFiles, SUBTITLE_FILES).toArray(new File[0]);
Movie[] movieByFileHash = new Movie[movieFiles.length]; 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>>();
if (movieFiles.length > 0 && query == null) {
// match movie hashes online // match movie hashes online
Map<File, Movie> movieByFile = new HashMap<File, Movie>();
if (query == null && movieFiles.size() > 0) {
try { try {
CLILogger.fine(format("Looking up movie by filehash via [%s]", service.getName())); CLILogger.fine(format("Looking up movie by filehash via [%s]", service.getName()));
movieByFileHash = service.getMovieDescriptors(movieFiles, locale); Map<File, Movie> hashLookup = service.getMovieDescriptors(movieFiles, locale);
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", movieByFileHash.length - frequency(asList(movieByFileHash), null)); // number of positive hash lookups movieByFile.putAll(hashLookup);
Analytics.trackEvent(service.getName(), "HashLookup", "Movie", hashLookup.size()); // number of positive hash lookups
} catch (UnsupportedOperationException e) { } catch (UnsupportedOperationException e) {
CLILogger.fine(format("%s: Hash lookup not supported", service.getName())); 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) { if (query != null) {
CLILogger.fine(format("Looking up movie by query [%s]", query)); CLILogger.fine(format("Looking up movie by query [%s]", query));
Movie result = (Movie) selectSearchResult(query, service.searchMovie(query, locale), strict).get(0); 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 // map all files by movie
for (int i = 0; i < movieFiles.length; i++) { for (File file : movieMatchFiles) {
Movie movie = movieByFileHash[i]; Movie movie = movieByFile.get(file);
// unknown hash, try via imdb id from nfo file // unknown hash, try via imdb id from nfo file
if (movie == null) { if (movie == null) {
CLILogger.fine(format("Auto-detect movie from context: [%s]", movieFiles[i])); CLILogger.fine(format("Auto-detect movie from context: [%s]", file));
Collection<Movie> results = detectMovie(movieFiles[i], null, service, locale, strict); Collection<Movie> results = detectMovie(file, null, service, locale, strict);
movie = (Movie) selectSearchResult(query, results, strict).get(0); movie = (Movie) selectSearchResult(query, results, strict).get(0);
if (movie != null) { if (movie != null) {
@ -320,7 +339,7 @@ public class CmdlineOperations implements CmdlineInterface {
filesByMovie.put(movie, movieParts); 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)); matches.add(new Match<File, Movie>(file, part));
}
}
// handle subtitle files // automatically add matches for derivates
for (File subtitle : subtitleFiles) { List<File> derivates = derivatesByMovieFile.get(file);
// check if subtitle corresponds to a movie file (same name, different extension) if (derivates != null) {
for (Match<File, ?> movieMatch : matches) { for (File derivate : derivates) {
if (isDerived(subtitle, movieMatch.getValue())) { matches.add(new Match<File, Movie>(derivate, part));
matches.add(new Match<File, Object>(subtitle, movieMatch.getCandidate())); }
// movie match found, we're done
break;
} }
} }
} }
@ -387,7 +402,7 @@ public class CmdlineOperations implements CmdlineInterface {
for (Entry<File, File> it : renameMap.entrySet()) { for (Entry<File, File> it : renameMap.entrySet()) {
try { try {
// rename file, throw exception on failure // 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())); CLILogger.info(format("Renamed [%s] to [%s]", it.getKey(), it.getValue()));
// remember successfully renamed matches for history entry and possible revert // 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.isDerived = { f -> isDerived(delegate, f) }
File.metaClass.validateFileName = { validateFileName(delegate) } File.metaClass.validateFileName = { validateFileName(delegate) }
File.metaClass.validateFilePath = { validateFilePath(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.mapByFolder = { mapByFolder(delegate) }
List.metaClass.mapByExtension = { mapByExtension(delegate) } List.metaClass.mapByExtension = { mapByExtension(delegate) }
String.metaClass.getExtension = { getExtension(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 static net.sourceforge.tuned.FileUtilities.*;
import java.io.File; import java.io.File;
import java.io.FileFilter;
import java.io.IOException; import java.io.IOException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
@ -49,6 +50,11 @@ public class MediaDetection {
private static final ReleaseInfo releaseInfo = new ReleaseInfo(); 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 { 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)); 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>(); Set<Movie> options = new LinkedHashSet<Movie>();
// lookup by file hash // lookup by file hash
if (hashLookupService != null) { if (hashLookupService != null && movieFile.isFile()) {
for (Movie movie : hashLookupService.getMovieDescriptors(new File[] { movieFile }, locale)) { for (Movie movie : hashLookupService.getMovieDescriptors(singleton(movieFile), locale).values()) {
if (movie != null) { if (movie != null) {
options.add(movie); options.add(movie);
} }

View File

@ -2,13 +2,13 @@
package net.sourceforge.filebot.media; package net.sourceforge.filebot.media;
import static java.util.Arrays.*;
import static java.util.ResourceBundle.*; import static java.util.ResourceBundle.*;
import static java.util.regex.Pattern.*; import static java.util.regex.Pattern.*;
import static net.sourceforge.filebot.similarity.Normalization.*; import static net.sourceforge.filebot.similarity.Normalization.*;
import static net.sourceforge.tuned.StringUtilities.*; import static net.sourceforge.tuned.StringUtilities.*;
import java.io.File; import java.io.File;
import java.io.FileFilter;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -125,7 +125,7 @@ public class ReleaseInfo {
languageMap.put(locale.getISO3Language(), locale); languageMap.put(locale.getISO3Language(), locale);
// map display language names for given locales // map display language names for given locales
for (Locale language : asList(supportedLanguageName)) { for (Locale language : supportedLanguageName) {
languageMap.put(locale.getDisplayLanguage(language), locale); 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 // 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[]> releaseGroupResource = new PatternResource(getBundle(getClass().getName()).getString("url.release-groups"));
protected final CachedResource<String[]> queryBlacklistResource = new PatternResource(getBundle(getClass().getName()).getString("url.query-blacklist")); 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) # list of all movies (id, name, year)
url.movie-list: http://filebot.sourceforge.net/data/movies.txt.gz 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; package net.sourceforge.filebot.ui.rename;
import static net.sourceforge.tuned.FileUtilities.*; import static java.util.Arrays.*;
import java.io.File; import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import net.sourceforge.filebot.media.MediaDetection;
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy; import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
import net.sourceforge.tuned.FastFile; import net.sourceforge.tuned.FastFile;
@ -35,7 +38,23 @@ class FilesListTransferablePolicy extends FileTransferablePolicy {
@Override @Override
protected void load(List<File> files) { 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));
} }

View File

@ -27,8 +27,8 @@ import java.util.EnumSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -50,8 +50,8 @@ import javax.swing.JTable;
import javax.swing.JTextField; import javax.swing.JTextField;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import javax.swing.RowFilter; import javax.swing.RowFilter;
import javax.swing.SortOrder;
import javax.swing.RowSorter.SortKey; import javax.swing.RowSorter.SortKey;
import javax.swing.SortOrder;
import javax.swing.border.CompoundBorder; import javax.swing.border.CompoundBorder;
import javax.swing.border.TitledBorder; import javax.swing.border.TitledBorder;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
@ -507,7 +507,7 @@ class HistoryDialog extends JDialog {
for (Entry<File, File> entry : getRenameMap(directory).entrySet()) { for (Entry<File, File> entry : getRenameMap(directory).entrySet()) {
try { try {
renameFile(entry.getKey(), entry.getValue()); moveRename(entry.getKey(), entry.getValue());
count++; count++;
} catch (Exception e) { } catch (Exception e) {
Logger.getLogger(HistoryDialog.class.getName()).log(Level.SEVERE, e.getMessage(), e); Logger.getLogger(HistoryDialog.class.getName()).log(Level.SEVERE, e.getMessage(), e);

View File

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

View File

@ -13,16 +13,16 @@ import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.io.File; import java.io.File;
import java.util.AbstractList; import java.util.AbstractList;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.logging.Level; import java.util.logging.Level;
@ -34,8 +34,8 @@ import javax.swing.SwingWorker;
import net.sourceforge.filebot.Analytics; import net.sourceforge.filebot.Analytics;
import net.sourceforge.filebot.ResourceManager; import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.tuned.ui.ProgressDialog; import net.sourceforge.tuned.ui.ProgressDialog;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
import net.sourceforge.tuned.ui.ProgressDialog.Cancellable; import net.sourceforge.tuned.ui.ProgressDialog.Cancellable;
import net.sourceforge.tuned.ui.SwingWorkerPropertyChangeAdapter;
class RenameAction extends AbstractAction { class RenameAction extends AbstractAction {
@ -209,7 +209,7 @@ class RenameAction extends AbstractAction {
firePropertyChange("currentFile", mapping.getKey(), mapping.getValue()); firePropertyChange("currentFile", mapping.getKey(), mapping.getValue());
// rename file, throw exception on failure // 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 // remember successfully renamed matches for history entry and possible revert
renameLog.put(mapping.getKey(), mapping.getValue()); renameLog.put(mapping.getKey(), mapping.getValue());

View File

@ -10,8 +10,10 @@ import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -122,7 +124,7 @@ public class IMDbClient implements MovieIdentificationService {
@Override @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(); throw new UnsupportedOperationException();
} }

View File

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

View File

@ -13,6 +13,7 @@ import java.io.Serializable;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.List; import java.util.List;
import java.util.Locale; 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 { public List<Movie> searchMovie(String hash, long bytesize, Locale locale) throws IOException, SAXException {
return getMovies("Media.getInfo", hash + "/" + bytesize, locale); return getMovies("Media.getInfo", hash + "/" + bytesize, locale);
} }
@ -96,17 +92,8 @@ public class TMDbClient implements MovieIdentificationService {
@Override @Override
public Movie[] getMovieDescriptors(File[] movieFiles, Locale locale) throws Exception { public Map<File, Movie> getMovieDescriptors(Collection<File> movieFiles, Locale locale) throws Exception {
Movie[] movies = new Movie[movieFiles.length]; throw new UnsupportedOperationException();
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;
} }

View File

@ -13,7 +13,6 @@ public class FastFile extends File {
private Long length; private Long length;
private Boolean isDirectory; private Boolean isDirectory;
private Boolean isFile; private Boolean isFile;
private Boolean exists;
public FastFile(String path) { public FastFile(String path) {
@ -32,12 +31,6 @@ public class FastFile extends File {
} }
@Override
public boolean exists() {
return exists != null ? exists : (exists = super.exists());
}
@Override @Override
public boolean isDirectory() { public boolean isDirectory() {
return isDirectory != null ? isDirectory : (isDirectory = super.isDirectory()); return isDirectory != null ? isDirectory : (isDirectory = super.isDirectory());

View File

@ -42,7 +42,7 @@ import com.ibm.icu.text.CharsetMatch;
public final class FileUtilities { 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 // resolve destination
if (!destination.isAbsolute()) { if (!destination.isAbsolute()) {
// same folder, different name // same folder, different name
@ -57,25 +57,37 @@ public final class FileUtilities {
throw new IOException("Failed to create folder: " + destinationFolder); throw new IOException("Failed to create folder: " + destinationFolder);
} }
if (source.isDirectory()) { // move folder
moveFolderIO(source, destination);
} else { // move file
try { try {
renameFileNIO2(source, destination); moveFileNIO2(source, destination);
} catch (LinkageError e) { } catch (LinkageError e) {
renameFileIO(source, destination); moveFileIO(source, destination);
}
} }
return 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()); 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)) { if (!source.renameTo(destination)) {
// try using Guava IO utilities, that'll just copy files if renameTo() fails // use "copy and delete" as fallback if standard rename fails
com.google.common.io.Files.move(source, destination); 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);
} }
} }