1
0
mirror of https://github.com/mitb-archive/filebot synced 2025-01-11 13:58:16 -05:00

* exclude trailer/sample files from processing as is done for movies already in episode mode as well

This commit is contained in:
Reinhard Pointner 2013-10-07 18:52:57 +00:00
parent 7a11589bc4
commit 6b5b757cfa

View File

@ -1,7 +1,5 @@
package net.sourceforge.filebot.ui.rename; package net.sourceforge.filebot.ui.rename;
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.Settings.*; import static net.sourceforge.filebot.Settings.*;
@ -52,31 +50,28 @@ import net.sourceforge.filebot.web.EpisodeListProvider;
import net.sourceforge.filebot.web.SearchResult; import net.sourceforge.filebot.web.SearchResult;
import net.sourceforge.filebot.web.SortOrder; import net.sourceforge.filebot.web.SortOrder;
class EpisodeListMatcher implements AutoCompleteMatcher { class EpisodeListMatcher implements AutoCompleteMatcher {
private final EpisodeListProvider provider; private final EpisodeListProvider provider;
// only allow one fetch session at a time so later requests can make use of cached results // only allow one fetch session at a time so later requests can make use of cached results
private final Object providerLock = new Object(); private final Object providerLock = new Object();
public EpisodeListMatcher(EpisodeListProvider provider) { public EpisodeListMatcher(EpisodeListProvider provider) {
this.provider = provider; this.provider = provider;
} }
protected SearchResult selectSearchResult(final String query, final List<SearchResult> searchResults, Map<String, SearchResult> selectionMemory, final Component parent) throws Exception { protected SearchResult selectSearchResult(final String query, final List<SearchResult> searchResults, Map<String, SearchResult> selectionMemory, final Component parent) throws Exception {
if (searchResults.size() == 1) { if (searchResults.size() == 1) {
return searchResults.get(0); return searchResults.get(0);
} }
// auto-select most probable search result // auto-select most probable search result
List<SearchResult> probableMatches = new LinkedList<SearchResult>(); List<SearchResult> probableMatches = new LinkedList<SearchResult>();
// use name similarity metric // use name similarity metric
SimilarityMetric metric = new NameSimilarityMetric(); SimilarityMetric metric = new NameSimilarityMetric();
// find probable matches using name similarity >= 0.85 // find probable matches using name similarity >= 0.85
for (SearchResult result : searchResults) { for (SearchResult result : searchResults) {
// remove trailing braces, e.g. Doctor Who (2005) -> Doctor Who // remove trailing braces, e.g. Doctor Who (2005) -> Doctor Who
@ -84,99 +79,98 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
probableMatches.add(result); probableMatches.add(result);
} }
} }
// auto-select first and only probable search result // auto-select first and only probable search result
if (probableMatches.size() == 1) { if (probableMatches.size() == 1) {
return probableMatches.get(0); return probableMatches.get(0);
} }
// show selection dialog on EDT // show selection dialog on EDT
final RunnableFuture<SearchResult> showSelectDialog = new FutureTask<SearchResult>(new Callable<SearchResult>() { final RunnableFuture<SearchResult> showSelectDialog = new FutureTask<SearchResult>(new Callable<SearchResult>() {
@Override @Override
public SearchResult call() throws Exception { public SearchResult call() throws Exception {
// multiple results have been found, user must select one // multiple results have been found, user must select one
SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(parent, searchResults); SelectDialog<SearchResult> selectDialog = new SelectDialog<SearchResult>(parent, searchResults);
selectDialog.getHeaderLabel().setText(String.format("Shows matching '%s':", query)); selectDialog.getHeaderLabel().setText(String.format("Shows matching '%s':", query));
selectDialog.getCancelAction().putValue(Action.NAME, "Ignore"); selectDialog.getCancelAction().putValue(Action.NAME, "Ignore");
// restore original dialog size // restore original dialog size
Settings prefs = Settings.forPackage(EpisodeListMatcher.class); Settings prefs = Settings.forPackage(EpisodeListMatcher.class);
int w = Integer.parseInt(prefs.get("dialog.select.w", "280")); int w = Integer.parseInt(prefs.get("dialog.select.w", "280"));
int h = Integer.parseInt(prefs.get("dialog.select.h", "300")); int h = Integer.parseInt(prefs.get("dialog.select.h", "300"));
selectDialog.setPreferredSize(new Dimension(w, h)); selectDialog.setPreferredSize(new Dimension(w, h));
selectDialog.pack(); selectDialog.pack();
// show dialog // show dialog
selectDialog.setLocation(getOffsetLocation(selectDialog.getOwner())); selectDialog.setLocation(getOffsetLocation(selectDialog.getOwner()));
selectDialog.setVisible(true); selectDialog.setVisible(true);
// remember dialog size // remember dialog size
prefs.put("dialog.select.w", Integer.toString(selectDialog.getWidth())); prefs.put("dialog.select.w", Integer.toString(selectDialog.getWidth()));
prefs.put("dialog.select.h", Integer.toString(selectDialog.getHeight())); prefs.put("dialog.select.h", Integer.toString(selectDialog.getHeight()));
// selected value or null if the dialog was canceled by the user // selected value or null if the dialog was canceled by the user
return selectDialog.getSelectedValue(); return selectDialog.getSelectedValue();
} }
}); });
// allow only one select dialog at a time // allow only one select dialog at a time
synchronized (this) { synchronized (this) {
synchronized (selectionMemory) { synchronized (selectionMemory) {
if (selectionMemory.containsKey(query)) { if (selectionMemory.containsKey(query)) {
return selectionMemory.get(query); return selectionMemory.get(query);
} }
SwingUtilities.invokeAndWait(showSelectDialog); SwingUtilities.invokeAndWait(showSelectDialog);
// cache selected value // cache selected value
selectionMemory.put(query, showSelectDialog.get()); selectionMemory.put(query, showSelectDialog.get());
return showSelectDialog.get(); return showSelectDialog.get();
} }
} }
} }
protected Set<Episode> fetchEpisodeSet(Collection<String> seriesNames, final SortOrder sortOrder, final Locale locale, final Map<String, SearchResult> selectionMemory, final Component parent) throws Exception { protected Set<Episode> fetchEpisodeSet(Collection<String> seriesNames, final SortOrder sortOrder, final Locale locale, final Map<String, SearchResult> selectionMemory, final Component parent) throws Exception {
List<Callable<List<Episode>>> tasks = new ArrayList<Callable<List<Episode>>>(); List<Callable<List<Episode>>> tasks = new ArrayList<Callable<List<Episode>>>();
// detect series names and create episode list fetch tasks // detect series names and create episode list fetch tasks
for (final String query : seriesNames) { for (final String query : seriesNames) {
tasks.add(new Callable<List<Episode>>() { tasks.add(new Callable<List<Episode>>() {
@Override @Override
public List<Episode> call() throws Exception { public List<Episode> call() throws Exception {
List<SearchResult> results = provider.search(query, locale); List<SearchResult> results = provider.search(query, locale);
// select search result // select search result
if (results.size() > 0) { if (results.size() > 0) {
SearchResult selectedSearchResult = selectSearchResult(query, results, selectionMemory, parent); SearchResult selectedSearchResult = selectSearchResult(query, results, selectionMemory, parent);
if (selectedSearchResult != null) { if (selectedSearchResult != null) {
List<Episode> episodes = provider.getEpisodeList(selectedSearchResult, sortOrder, locale); List<Episode> episodes = provider.getEpisodeList(selectedSearchResult, sortOrder, locale);
Analytics.trackEvent(provider.getName(), "FetchEpisodeList", selectedSearchResult.getName()); Analytics.trackEvent(provider.getName(), "FetchEpisodeList", selectedSearchResult.getName());
return episodes; return episodes;
} }
} }
return Collections.emptyList(); return Collections.emptyList();
} }
}); });
} }
// fetch episode lists concurrently // fetch episode lists concurrently
ExecutorService executor = Executors.newCachedThreadPool(); ExecutorService executor = Executors.newCachedThreadPool();
try { try {
// merge all episodes // merge all episodes
Set<Episode> episodes = new LinkedHashSet<Episode>(); Set<Episode> episodes = new LinkedHashSet<Episode>();
for (Future<List<Episode>> future : executor.invokeAll(tasks)) { for (Future<List<Episode>> future : executor.invokeAll(tasks)) {
episodes.addAll(future.get()); episodes.addAll(future.get());
} }
// all background workers have finished // all background workers have finished
return episodes; return episodes;
} finally { } finally {
@ -184,25 +178,27 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
executor.shutdownNow(); executor.shutdownNow();
} }
} }
@Override @Override
public List<Match<File, ?>> match(final List<File> files, final SortOrder sortOrder, final Locale locale, final boolean autodetection, final Component parent) throws Exception { public List<Match<File, ?>> match(List<File> files, final SortOrder sortOrder, final Locale locale, final boolean autodetection, final Component parent) throws Exception {
// ignore sample files
final List<File> fileset = filter(files, not(getClutterFileFilter()));
// focus on movie and subtitle files // focus on movie and subtitle files
final List<File> mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES); final List<File> mediaFiles = filter(fileset, VIDEO_FILES, SUBTITLE_FILES);
// assume that many shows will be matched, do it folder by folder // assume that many shows will be matched, do it folder by folder
List<Callable<List<Match<File, ?>>>> taskPerFolder = new ArrayList<Callable<List<Match<File, ?>>>>(); List<Callable<List<Match<File, ?>>>> taskPerFolder = new ArrayList<Callable<List<Match<File, ?>>>>();
// remember user decisions and only bother user once // remember user decisions and only bother user once
final Map<String, SearchResult> selectionMemory = new TreeMap<String, SearchResult>(CommonSequenceMatcher.getLenientCollator(Locale.ROOT)); final Map<String, SearchResult> selectionMemory = new TreeMap<String, SearchResult>(CommonSequenceMatcher.getLenientCollator(Locale.ROOT));
final Map<String, List<String>> inputMemory = new TreeMap<String, List<String>>(CommonSequenceMatcher.getLenientCollator(Locale.ROOT)); final Map<String, List<String>> inputMemory = new TreeMap<String, List<String>>(CommonSequenceMatcher.getLenientCollator(Locale.ROOT));
// detect series names and create episode list fetch tasks // detect series names and create episode list fetch tasks
for (Entry<Set<File>, Set<String>> sameSeriesGroup : mapSeriesNamesByFiles(mediaFiles, locale).entrySet()) { for (Entry<Set<File>, Set<String>> sameSeriesGroup : mapSeriesNamesByFiles(mediaFiles, locale).entrySet()) {
final List<List<File>> batchSets = new ArrayList<List<File>>(); final List<List<File>> batchSets = new ArrayList<List<File>>();
final Collection<String> queries = sameSeriesGroup.getValue(); final Collection<String> queries = sameSeriesGroup.getValue();
if (queries != null && queries.size() > 0) { if (queries != null && queries.size() > 0) {
// handle series name batch set all at once -> only 1 batch set // handle series name batch set all at once -> only 1 batch set
batchSets.add(new ArrayList<File>(sameSeriesGroup.getKey())); batchSets.add(new ArrayList<File>(sameSeriesGroup.getKey()));
@ -210,10 +206,10 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
// these files don't seem to belong to any series -> handle folder per folder -> multiple batch sets // these files don't seem to belong to any series -> handle folder per folder -> multiple batch sets
batchSets.addAll(mapByFolder(sameSeriesGroup.getKey()).values()); batchSets.addAll(mapByFolder(sameSeriesGroup.getKey()).values());
} }
for (final List<File> batchSet : batchSets) { for (final List<File> batchSet : batchSets) {
taskPerFolder.add(new Callable<List<Match<File, ?>>>() { taskPerFolder.add(new Callable<List<Match<File, ?>>>() {
@Override @Override
public List<Match<File, ?>> call() throws Exception { public List<Match<File, ?>> call() throws Exception {
return matchEpisodeSet(batchSet, queries, sortOrder, locale, autodetection, selectionMemory, inputMemory, parent); return matchEpisodeSet(batchSet, queries, sortOrder, locale, autodetection, selectionMemory, inputMemory, parent);
@ -221,10 +217,10 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
}); });
} }
} }
// match folder per folder in parallel // match folder per folder in parallel
ExecutorService executor = Executors.newFixedThreadPool(getPreferredThreadPoolSize()); ExecutorService executor = Executors.newFixedThreadPool(getPreferredThreadPoolSize());
try { try {
// merge all episodes // merge all episodes
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>(); List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
@ -234,12 +230,12 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone())); matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone()));
} }
} }
// handle derived files // handle derived files
List<Match<File, ?>> derivateMatches = new ArrayList<Match<File, ?>>(); List<Match<File, ?>> derivateMatches = new ArrayList<Match<File, ?>>();
SortedSet<File> derivateFiles = new TreeSet<File>(files); SortedSet<File> derivateFiles = new TreeSet<File>(fileset);
derivateFiles.removeAll(mediaFiles); derivateFiles.removeAll(mediaFiles);
for (File file : derivateFiles) { for (File file : derivateFiles) {
for (Match<File, ?> match : matches) { for (Match<File, ?> match : matches) {
if (file.getParentFile().equals(match.getValue().getParentFile()) && isDerived(file, match.getValue()) && match.getCandidate() instanceof Episode) { if (file.getParentFile().equals(match.getValue().getParentFile()) && isDerived(file, match.getValue()) && match.getCandidate() instanceof Episode) {
@ -248,19 +244,19 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
} }
} }
} }
// add matches from other files that are linked via filenames // add matches from other files that are linked via filenames
matches.addAll(derivateMatches); matches.addAll(derivateMatches);
// restore original order // restore original order
Collections.sort(matches, new Comparator<Match<File, ?>>() { Collections.sort(matches, new Comparator<Match<File, ?>>() {
@Override @Override
public int compare(Match<File, ?> o1, Match<File, ?> o2) { public int compare(Match<File, ?> o1, Match<File, ?> o2) {
return files.indexOf(o1.getValue()) - files.indexOf(o2.getValue()); return fileset.indexOf(o1.getValue()) - fileset.indexOf(o2.getValue());
} }
}); });
// all background workers have finished // all background workers have finished
return matches; return matches;
} finally { } finally {
@ -268,11 +264,10 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
executor.shutdownNow(); executor.shutdownNow();
} }
} }
public List<Match<File, ?>> matchEpisodeSet(final List<File> files, Collection<String> queries, SortOrder sortOrder, Locale locale, boolean autodetection, Map<String, SearchResult> selectionMemory, Map<String, List<String>> inputMemory, Component parent) throws Exception { public List<Match<File, ?>> matchEpisodeSet(final List<File> files, Collection<String> queries, SortOrder sortOrder, Locale locale, boolean autodetection, Map<String, SearchResult> selectionMemory, Map<String, List<String>> inputMemory, Component parent) throws Exception {
Set<Episode> episodes = emptySet(); Set<Episode> episodes = emptySet();
// detect series name and fetch episode list // detect series name and fetch episode list
if (autodetection) { if (autodetection) {
if (queries != null && queries.size() > 0) { if (queries != null && queries.size() > 0) {
@ -282,13 +277,13 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
} }
} }
} }
// require user input if auto-detection has failed or has been disabled // require user input if auto-detection has failed or has been disabled
if (episodes.isEmpty()) { if (episodes.isEmpty()) {
List<String> detectedSeriesNames = detectSeriesNames(files, locale); List<String> detectedSeriesNames = detectSeriesNames(files, locale);
String parentPathHint = normalizePathSeparators(getRelativePathTail(files.get(0).getParentFile(), 2).getPath()); String parentPathHint = normalizePathSeparators(getRelativePathTail(files.get(0).getParentFile(), 2).getPath());
String suggestion = detectedSeriesNames.size() > 0 ? join(detectedSeriesNames, ", ") : parentPathHint; String suggestion = detectedSeriesNames.size() > 0 ? join(detectedSeriesNames, ", ") : parentPathHint;
List<String> input = emptyList(); List<String> input = emptyList();
synchronized (inputMemory) { synchronized (inputMemory) {
input = inputMemory.get(suggestion); input = inputMemory.get(suggestion);
@ -297,7 +292,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
inputMemory.put(suggestion, input); inputMemory.put(suggestion, input);
} }
} }
if (input.size() > 0) { if (input.size() > 0) {
// only allow one fetch session at a time so later requests can make use of cached results // only allow one fetch session at a time so later requests can make use of cached results
synchronized (providerLock) { synchronized (providerLock) {
@ -305,10 +300,10 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
} }
} }
} }
// find file/episode matches // find file/episode matches
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>(); List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
// group by subtitles first and then by files in general // group by subtitles first and then by files in general
for (List<File> filesPerType : mapByExtension(files).values()) { for (List<File> filesPerType : mapByExtension(files).values()) {
EpisodeMatcher matcher = new EpisodeMatcher(filesPerType, episodes, false); EpisodeMatcher matcher = new EpisodeMatcher(filesPerType, episodes, false);
@ -316,7 +311,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone())); matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone()));
} }
} }
return matches; return matches;
} }
} }