* massive performance improvements
* proper parallel processing in movie mode
This commit is contained in:
parent
9c8e720f2a
commit
9e6883b646
|
@ -25,9 +25,7 @@ import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.SortedMap;
|
import java.util.SortedMap;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
@ -206,16 +204,10 @@ public class MediaDetection {
|
||||||
|
|
||||||
private static List<Movie> matchMovieName(final List<String> files, final Locale locale, final boolean strict) throws Exception {
|
private static List<Movie> matchMovieName(final List<String> files, final Locale locale, final boolean strict) throws Exception {
|
||||||
// cross-reference file / folder name with movie list
|
// cross-reference file / folder name with movie list
|
||||||
final SeriesNameMatcher nameMatcher = new SeriesNameMatcher(String.CASE_INSENSITIVE_ORDER); // use simple comparator for speed (2-3x faster)
|
final HighPerformanceMatcher nameMatcher = new HighPerformanceMatcher();
|
||||||
|
final Map<Movie, String> matchMap = new HashMap<Movie, String>();
|
||||||
final Map<Movie, String> matchMap = synchronizedMap(new HashMap<Movie, String>());
|
|
||||||
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
|
||||||
|
|
||||||
for (final Movie movie : releaseInfo.getMovieList()) {
|
for (final Movie movie : releaseInfo.getMovieList()) {
|
||||||
executor.submit(new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
for (String name : files) {
|
for (String name : files) {
|
||||||
String movieIdentifier = movie.getName();
|
String movieIdentifier = movie.getName();
|
||||||
String commonName = nameMatcher.matchByFirstCommonWordSequence(name, movieIdentifier);
|
String commonName = nameMatcher.matchByFirstCommonWordSequence(name, movieIdentifier);
|
||||||
|
@ -232,12 +224,6 @@ public class MediaDetection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for last task to finish
|
|
||||||
executor.shutdown();
|
|
||||||
executor.awaitTermination(1, TimeUnit.MINUTES);
|
|
||||||
|
|
||||||
// sort by length of name match (descending)
|
// sort by length of name match (descending)
|
||||||
List<Movie> results = new ArrayList<Movie>(matchMap.keySet());
|
List<Movie> results = new ArrayList<Movie>(matchMap.keySet());
|
||||||
|
@ -365,6 +351,7 @@ public class MediaDetection {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public static Comparator<String> getLenientCollator(Locale locale) {
|
public static Comparator<String> getLenientCollator(Locale locale) {
|
||||||
// use maximum strength collator by default
|
// use maximum strength collator by default
|
||||||
final Collator collator = Collator.getInstance(locale);
|
final Collator collator = Collator.getInstance(locale);
|
||||||
|
@ -373,4 +360,30 @@ public class MediaDetection {
|
||||||
|
|
||||||
return (Comparator) collator;
|
return (Comparator) collator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Heavy-duty name matcher used for matching a file to or more movies (out of a list of ~50k)
|
||||||
|
*/
|
||||||
|
private static class HighPerformanceMatcher extends SeriesNameMatcher {
|
||||||
|
|
||||||
|
private static final Map<String, String> transformCache = synchronizedMap(new WeakHashMap<String, String>(65536));
|
||||||
|
|
||||||
|
|
||||||
|
public HighPerformanceMatcher() {
|
||||||
|
super(String.CASE_INSENSITIVE_ORDER); // 3-4x faster than a Collator
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String normalize(String source) {
|
||||||
|
String value = transformCache.get(source);
|
||||||
|
if (value == null) {
|
||||||
|
value = super.normalize(source);
|
||||||
|
transformCache.put(source, value);
|
||||||
|
}
|
||||||
|
return transformCache.get(source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
||||||
|
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.util.AbstractMap.SimpleEntry;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -26,6 +27,9 @@ import java.util.Set;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.FutureTask;
|
import java.util.concurrent.FutureTask;
|
||||||
import java.util.concurrent.RunnableFuture;
|
import java.util.concurrent.RunnableFuture;
|
||||||
|
|
||||||
|
@ -53,7 +57,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Match<File, ?>> match(final List<File> files, Locale locale, boolean autodetect, 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]);
|
File[] movieFiles = filter(files, VIDEO_FILES).toArray(new File[0]);
|
||||||
File[] subtitleFiles = filter(files, SUBTITLE_FILES).toArray(new File[0]);
|
File[] subtitleFiles = filter(files, SUBTITLE_FILES).toArray(new File[0]);
|
||||||
|
@ -73,21 +77,38 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
||||||
// 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>>();
|
||||||
|
|
||||||
|
// match remaining movies file by file in parallel
|
||||||
|
List<Callable<Entry<File, Movie>>> grabMovieJobs = new ArrayList<Callable<Entry<File, Movie>>>();
|
||||||
|
|
||||||
// map all files by movie
|
// map all files by movie
|
||||||
for (int i = 0; i < movieFiles.length; i++) {
|
for (int i = 0; i < movieFiles.length; i++) {
|
||||||
Movie movie = movieByFileHash[i];
|
final Movie movie = movieByFileHash[i];
|
||||||
|
final File file = movieFiles[i];
|
||||||
|
grabMovieJobs.add(new Callable<Entry<File, Movie>>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Entry<File, Movie> call() throws Exception {
|
||||||
// unknown hash, try via imdb id from nfo file
|
// unknown hash, try via imdb id from nfo file
|
||||||
if (movie == null || !autodetect) {
|
if (movie == null || !autodetect) {
|
||||||
movie = grabMovieName(movieFiles[i], locale, autodetect, parent, movie);
|
Movie result = grabMovieName(file, locale, autodetect, parent, movie);
|
||||||
|
if (result != null) {
|
||||||
if (movie != null) {
|
Analytics.trackEvent(service.getName(), "SearchMovie", result.toString(), 1);
|
||||||
Analytics.trackEvent(service.getName(), "SearchMovie", movie.toString(), 1);
|
|
||||||
}
|
}
|
||||||
|
return new SimpleEntry<File, Movie>(file, result);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||||
|
try {
|
||||||
|
for (Future<Entry<File, Movie>> it : executor.invokeAll(grabMovieJobs)) {
|
||||||
// check if we managed to lookup the movie descriptor
|
// check if we managed to lookup the movie descriptor
|
||||||
if (movie != null) {
|
if (it.get() != null) {
|
||||||
|
File file = it.get().getKey();
|
||||||
|
Movie movie = it.get().getValue();
|
||||||
|
|
||||||
// get file list for movie
|
// get file list for movie
|
||||||
SortedSet<File> movieParts = filesByMovie.get(movie);
|
SortedSet<File> movieParts = filesByMovie.get(movie);
|
||||||
|
|
||||||
|
@ -96,9 +117,12 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
||||||
filesByMovie.put(movie, movieParts);
|
filesByMovie.put(movie, movieParts);
|
||||||
}
|
}
|
||||||
|
|
||||||
movieParts.add(movieFiles[i]);
|
movieParts.add(file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
// collect all File/MoviePart matches
|
// collect all File/MoviePart matches
|
||||||
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
||||||
|
|
Loading…
Reference in New Issue