+ support for adjustable match mode Opportunistic (default, like always) and new Strict mode (which is very restrictive, but will most likely get it right, if it gets anything at all)

This commit is contained in:
Reinhard Pointner 2014-08-13 16:02:35 +00:00
parent 425bfb83ea
commit 097e001111
6 changed files with 123 additions and 62 deletions

View File

@ -620,7 +620,7 @@ public class MediaDetection {
return sortBySimilarity(options, terms, getMovieMatchMetric(), true); return sortBySimilarity(options, terms, getMovieMatchMetric(), true);
} }
// if matching name+year failed, try matching only by name // if matching name+year failed, try matching only by name (in non-strict mode we would have checked these cases already by now)
if (movieNameMatches.isEmpty() && strict) { if (movieNameMatches.isEmpty() && strict) {
movieNameMatches = matchMovieName(terms, false, 0); movieNameMatches = matchMovieName(terms, false, 0);
if (movieNameMatches.isEmpty()) { if (movieNameMatches.isEmpty()) {
@ -736,6 +736,19 @@ public class MediaDetection {
return result; return result;
} }
public static List<Integer> parseMovieYear(String name) {
List<Integer> years = new ArrayList<Integer>();
for (String it : name.split("\\D+")) {
if (it.length() == 4) {
int year = Integer.parseInt(it);
if (1920 < year && year < 2050) {
years.add(year);
}
}
}
return years;
}
public static String reduceMovieName(String name, boolean strict) throws IOException { public static String reduceMovieName(String name, boolean strict) throws IOException {
Matcher matcher = compile(strict ? "^(.+)[\\[\\(]((?:19|20)\\d{2})[\\]\\)]" : "^(.+?)((?:19|20)\\d{2})").matcher(name); Matcher matcher = compile(strict ? "^(.+)[\\[\\(]((?:19|20)\\d{2})[\\]\\)]" : "^(.+?)((?:19|20)\\d{2})").matcher(name);
if (matcher.find()) { if (matcher.find()) {

View File

@ -1,7 +1,5 @@
package net.filebot.ui.rename; package net.filebot.ui.rename;
import java.awt.Component; import java.awt.Component;
import java.io.File; import java.io.File;
import java.util.List; import java.util.List;
@ -10,8 +8,7 @@ import java.util.Locale;
import net.filebot.similarity.Match; import net.filebot.similarity.Match;
import net.filebot.web.SortOrder; import net.filebot.web.SortOrder;
interface AutoCompleteMatcher { interface AutoCompleteMatcher {
List<Match<File, ?>> match(List<File> files, SortOrder order, Locale locale, boolean autodetection, Component parent) throws Exception; List<Match<File, ?>> match(List<File> files, boolean strict, SortOrder order, Locale locale, boolean autodetection, Component parent) throws Exception;
} }

View File

@ -48,7 +48,7 @@ import net.filebot.web.SortOrder;
class EpisodeListMatcher implements AutoCompleteMatcher { class EpisodeListMatcher implements AutoCompleteMatcher {
private final EpisodeListProvider provider; private EpisodeListProvider provider;
private boolean useAnimeIndex; private boolean useAnimeIndex;
private boolean useSeriesIndex; private boolean useSeriesIndex;
@ -170,7 +170,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
} }
@Override @Override
public List<Match<File, ?>> match(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 boolean strict, final SortOrder sortOrder, final Locale locale, final boolean autodetection, final Component parent) throws Exception {
if (files.isEmpty()) { if (files.isEmpty()) {
return justFetchEpisodeList(sortOrder, locale, parent); return justFetchEpisodeList(sortOrder, locale, parent);
} }
@ -182,33 +182,49 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
final List<File> mediaFiles = filter(fileset, 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, ?>>>> tasks = 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, useSeriesIndex, useAnimeIndex).entrySet()) { if (strict) {
final List<List<File>> batchSets = new ArrayList<List<File>>(); // in strict mode simply process file-by-file (ignoring all files that don't contain clear SxE patterns)
final Collection<String> queries = sameSeriesGroup.getValue(); for (final File file : mediaFiles) {
if (parseEpisodeNumber(file, true) != null || parseDate(file) != null) {
tasks.add(new Callable<List<Match<File, ?>>>() {
if (queries != null && queries.size() > 0) { @Override
// handle series name batch set all at once -> only 1 batch set public List<Match<File, ?>> call() throws Exception {
batchSets.add(new ArrayList<File>(sameSeriesGroup.getKey())); return matchEpisodeSet(singletonList(file), detectSeriesNames(singleton(file), useSeriesIndex, useAnimeIndex, locale), sortOrder, strict, locale, autodetection, selectionMemory, inputMemory, parent);
} else { }
// these files don't seem to belong to any series -> handle folder per folder -> multiple batch sets });
batchSets.addAll(mapByFolder(sameSeriesGroup.getKey()).values()); }
} }
} else {
// in non-strict mode use the complicated (more powerful but also more error prone) match-batch-by-batch logic
for (Entry<Set<File>, Set<String>> sameSeriesGroup : mapSeriesNamesByFiles(mediaFiles, locale, useSeriesIndex, useAnimeIndex).entrySet()) {
final List<List<File>> batchSets = new ArrayList<List<File>>();
final Collection<String> queries = sameSeriesGroup.getValue();
for (final List<File> batchSet : batchSets) { if (queries != null && queries.size() > 0) {
taskPerFolder.add(new Callable<List<Match<File, ?>>>() { // handle series name batch set all at once -> only 1 batch set
batchSets.add(new ArrayList<File>(sameSeriesGroup.getKey()));
} else {
// these files don't seem to belong to any series -> handle folder per folder -> multiple batch sets
batchSets.addAll(mapByFolder(sameSeriesGroup.getKey()).values());
}
@Override for (final List<File> batchSet : batchSets) {
public List<Match<File, ?>> call() throws Exception { tasks.add(new Callable<List<Match<File, ?>>>() {
return matchEpisodeSet(batchSet, queries, sortOrder, locale, autodetection, selectionMemory, inputMemory, parent);
} @Override
}); public List<Match<File, ?>> call() throws Exception {
return matchEpisodeSet(batchSet, queries, sortOrder, strict, locale, autodetection, selectionMemory, inputMemory, parent);
}
});
}
} }
} }
@ -218,7 +234,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
try { try {
// merge all episodes // merge all episodes
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>(); List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
for (Future<List<Match<File, ?>>> future : executor.invokeAll(taskPerFolder)) { for (Future<List<Match<File, ?>>> future : executor.invokeAll(tasks)) {
// make sure each episode has unique object data // make sure each episode has unique object data
for (Match<File, ?> it : future.get()) { for (Match<File, ?> it : future.get()) {
matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone())); matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone()));
@ -259,7 +275,7 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
} }
} }
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, boolean strict, 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
@ -273,7 +289,7 @@ 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() && !strict) {
List<String> detectedSeriesNames = detectSeriesNames(files, useSeriesIndex, useAnimeIndex, locale); List<String> detectedSeriesNames = detectSeriesNames(files, useSeriesIndex, useAnimeIndex, 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;
@ -299,10 +315,12 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
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 : mapByMediaExtension(files).values()) { if (episodes.size() > 0) {
EpisodeMatcher matcher = new EpisodeMatcher(filesPerType, episodes, false); for (List<File> filesPerType : mapByMediaExtension(files).values()) {
for (Match<File, Object> it : matcher.match()) { EpisodeMatcher matcher = new EpisodeMatcher(filesPerType, episodes, strict);
matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone())); for (Match<File, Object> it : matcher.match()) {
matches.add(new Match<File, Episode>(it.getValue(), ((Episode) it.getCandidate()).clone()));
}
} }
} }

View File

@ -65,7 +65,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
} }
@Override @Override
public List<Match<File, ?>> match(final List<File> files, final SortOrder sortOrder, final Locale locale, final boolean autodetect, final Component parent) throws Exception { public List<Match<File, ?>> match(final List<File> files, final boolean strict, final SortOrder sortOrder, final Locale locale, final boolean autodetect, final Component parent) throws Exception {
if (files.isEmpty()) { if (files.isEmpty()) {
return justFetchMovieInfo(locale, parent); return justFetchMovieInfo(locale, parent);
} }
@ -183,7 +183,23 @@ class MovieHashMatcher implements AutoCompleteMatcher {
public Map<File, Collection<Movie>> call() throws Exception { public Map<File, Collection<Movie>> call() throws Exception {
Map<File, Collection<Movie>> detection = new LinkedHashMap<File, Collection<Movie>>(); Map<File, Collection<Movie>> detection = new LinkedHashMap<File, Collection<Movie>>();
for (File f : folder) { for (File f : folder) {
detection.put(f, detectMovie(f, null, service, locale, false)); if (strict) {
// in strict mode, only process movies that follow the name (year) pattern
List<Integer> year = parseMovieYear(getRelativePathTail(f, 3).getPath());
if (year.size() > 0) {
// allow only movie matches where the the movie year matches the year pattern in the filename
List<Movie> matches = new ArrayList<Movie>();
for (Movie movie : detectMovie(f, null, service, locale, strict)) {
if (year.contains(movie.getYear())) {
matches.add(movie);
}
}
detection.put(f, matches);
}
} else {
// in non-strict mode just allow all options
detection.put(f, detectMovie(f, null, service, locale, strict));
}
} }
return detection; return detection;
} }
@ -200,7 +216,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
// auto-select movie or ask user // auto-select movie or ask user
for (Entry<File, Collection<Movie>> it : detection.get().entrySet()) { for (Entry<File, Collection<Movie>> it : detection.get().entrySet()) {
File movieFile = it.getKey(); File movieFile = it.getKey();
Movie movie = grabMovieName(movieFile, it.getValue(), locale, autodetect, memory, parent); Movie movie = grabMovieName(movieFile, it.getValue(), strict, locale, autodetect, memory, parent);
if (movie != null) { if (movie != null) {
movieByFile.put(movieFile, movie); movieByFile.put(movieFile, movie);
} }
@ -262,13 +278,9 @@ class MovieHashMatcher implements AutoCompleteMatcher {
return matches; return matches;
} }
protected Movie grabMovieName(File movieFile, Collection<Movie> options, Locale locale, boolean autodetect, Map<String, Object> memory, Component parent) throws Exception { protected Movie grabMovieName(File movieFile, Collection<Movie> options, boolean strict, Locale locale, boolean autodetect, Map<String, Object> memory, Component parent) throws Exception {
// allow manual user input // allow manual user input
if (!autodetect || options.isEmpty()) { if (!strict && (!autodetect || options.isEmpty()) && !(autodetect && memory.containsKey("repeat"))) {
if (autodetect && memory.containsKey("repeat")) {
return null;
}
String suggestion = options.isEmpty() ? stripReleaseInfo(getName(movieFile)) : options.iterator().next().getName(); String suggestion = options.isEmpty() ? stripReleaseInfo(getName(movieFile)) : options.iterator().next().getName();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -284,12 +296,12 @@ class MovieHashMatcher implements AutoCompleteMatcher {
if (input != null) { if (input != null) {
options = service.searchMovie(input, locale); options = service.searchMovie(input, locale);
if (options.size() > 0) { if (options.size() > 0) {
return selectMovie(movieFile, input, options, memory, parent); return selectMovie(movieFile, strict, input, options, memory, parent);
} }
} }
} }
return options.isEmpty() ? null : selectMovie(movieFile, null, options, memory, parent); return options.isEmpty() ? null : selectMovie(movieFile, strict, null, options, memory, parent);
} }
protected String getQueryInputMessage(File file) throws Exception { protected String getQueryInputMessage(File file) throws Exception {
@ -314,12 +326,12 @@ class MovieHashMatcher implements AutoCompleteMatcher {
return html.toString(); return html.toString();
} }
protected String checkedStripReleaseInfo(File file) throws Exception { protected String checkedStripReleaseInfo(File file, boolean strict) throws Exception {
String name = stripReleaseInfo(getName(file)); String name = stripReleaseInfo(getName(file));
// try to redeem possible false negative matches // try to redeem possible false negative matches
if (name.length() < 2) { if (name.length() < 2) {
Movie match = checkMovie(file, false); Movie match = checkMovie(file, strict);
if (match != null) { if (match != null) {
return match.getName(); return match.getName();
} }
@ -328,18 +340,18 @@ class MovieHashMatcher implements AutoCompleteMatcher {
return name; return name;
} }
protected Movie selectMovie(final File movieFile, final String userQuery, final Collection<Movie> options, final Map<String, Object> memory, final Component parent) throws Exception { protected Movie selectMovie(final File movieFile, final boolean strict, final String userQuery, final Collection<Movie> options, final Map<String, Object> memory, final Component parent) throws Exception {
// just auto-pick singleton results // just auto-pick singleton results
if (options.size() == 1) { if (options.size() == 1) {
return options.iterator().next(); return options.iterator().next();
} }
// 1. movie by filename // 1. movie by filename
final String fileQuery = (userQuery != null) ? userQuery : checkedStripReleaseInfo(movieFile); final String fileQuery = (userQuery != null) ? userQuery : checkedStripReleaseInfo(movieFile, strict);
// 2. movie by directory // 2. movie by directory
final File movieFolder = guessMovieFolder(movieFile); final File movieFolder = guessMovieFolder(movieFile);
final String folderQuery = (userQuery != null || movieFolder == null) ? "" : checkedStripReleaseInfo(movieFolder); final String folderQuery = (userQuery != null || movieFolder == null) ? "" : checkedStripReleaseInfo(movieFolder, strict);
// auto-ignore invalid files // auto-ignore invalid files
if (userQuery == null && fileQuery.length() < 2 && folderQuery.length() < 2) { if (userQuery == null && fileQuery.length() < 2 && folderQuery.length() < 2) {
@ -364,7 +376,7 @@ class MovieHashMatcher implements AutoCompleteMatcher {
for (Movie result : options) { for (Movie result : options) {
float maxSimilarity = 0; float maxSimilarity = 0;
for (String query : new String[] { fileQuery, folderQuery }) { for (String query : new String[] { fileQuery, folderQuery }) {
for (String name : result.getEffectiveNamesWithoutYear()) { for (String name : strict ? result.getEffectiveNames() : result.getEffectiveNamesWithoutYear()) {
if (maxSimilarity >= threshold) if (maxSimilarity >= threshold)
continue; continue;
@ -381,6 +393,11 @@ class MovieHashMatcher implements AutoCompleteMatcher {
return probableMatches.get(0); return probableMatches.get(0);
} }
// if we haven't confirmed a match at this point then the file is probably badly named and should be ignored
if (strict) {
return null;
}
// show selection dialog on EDT // show selection dialog on EDT
final RunnableFuture<Movie> showSelectDialog = new FutureTask<Movie>(new Callable<Movie>() { final RunnableFuture<Movie> showSelectDialog = new FutureTask<Movie>(new Callable<Movie>() {

View File

@ -97,6 +97,7 @@ public class RenamePanel extends JComponent {
private static final PreferencesEntry<String> persistentFileFormat = Settings.forPackage(RenamePanel.class).entry("rename.format.file"); private static final PreferencesEntry<String> persistentFileFormat = Settings.forPackage(RenamePanel.class).entry("rename.format.file");
private static final PreferencesEntry<String> persistentLastFormatState = Settings.forPackage(RenamePanel.class).entry("rename.last.format.state"); private static final PreferencesEntry<String> persistentLastFormatState = Settings.forPackage(RenamePanel.class).entry("rename.last.format.state");
private static final PreferencesEntry<String> persistentPreferredMatchMode = Settings.forPackage(RenamePanel.class).entry("rename.match.mode").defaultValue("Opportunistic");
private static final PreferencesEntry<String> persistentPreferredLanguage = Settings.forPackage(RenamePanel.class).entry("rename.language").defaultValue("en"); private static final PreferencesEntry<String> persistentPreferredLanguage = Settings.forPackage(RenamePanel.class).entry("rename.language").defaultValue("en");
private static final PreferencesEntry<String> persistentPreferredEpisodeOrder = Settings.forPackage(RenamePanel.class).entry("rename.episode.order").defaultValue("Airdate"); private static final PreferencesEntry<String> persistentPreferredEpisodeOrder = Settings.forPackage(RenamePanel.class).entry("rename.episode.order").defaultValue("Airdate");
@ -392,6 +393,9 @@ public class RenamePanel extends JComponent {
@Override @Override
public void actionPerformed(ActionEvent evt) { public void actionPerformed(ActionEvent evt) {
String[] modes = new String[] { "Opportunistic", "Strict" };
JComboBox modeCombo = new JComboBox(modes);
List<Language> languages = new ArrayList<Language>(); List<Language> languages = new ArrayList<Language>();
languages.addAll(Language.preferredLanguages()); // add preferred languages first languages.addAll(Language.preferredLanguages()); // add preferred languages first
languages.addAll(Language.availableLanguages()); // then others languages.addAll(Language.availableLanguages()); // then others
@ -411,31 +415,41 @@ public class RenamePanel extends JComponent {
} }
}); });
// pre-select current preferences // restore current preference values
try { try {
orderCombo.setSelectedItem(SortOrder.forName(persistentPreferredEpisodeOrder.getValue())); modeCombo.setSelectedItem(persistentPreferredMatchMode.getValue());
} catch (IllegalArgumentException e) { for (Language language : languages) {
// ignore if (language.getCode().equals(persistentPreferredLanguage.getValue())) {
} languageList.setSelectedValue(language, true);
for (Language language : languages) { break;
if (language.getCode().equals(persistentPreferredLanguage.getValue())) { }
languageList.setSelectedValue(language, true);
break;
} }
orderCombo.setSelectedItem(SortOrder.forName(persistentPreferredEpisodeOrder.getValue()));
} catch (Exception e) {
Logger.getLogger(RenamePanel.class.getName()).log(Level.WARNING, e.getMessage(), e);
} }
JScrollPane spModeCombo = new JScrollPane(modeCombo, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
spModeCombo.setBorder(new CompoundBorder(new TitledBorder("Match Mode"), spModeCombo.getBorder()));
JScrollPane spLanguageList = new JScrollPane(languageList); JScrollPane spLanguageList = new JScrollPane(languageList);
spLanguageList.setBorder(new CompoundBorder(new TitledBorder("Preferred Language"), spLanguageList.getBorder())); spLanguageList.setBorder(new CompoundBorder(new TitledBorder("Language"), spLanguageList.getBorder()));
JScrollPane spOrderCombo = new JScrollPane(orderCombo, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); JScrollPane spOrderCombo = new JScrollPane(orderCombo, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
spOrderCombo.setBorder(new CompoundBorder(new TitledBorder("Preferred Episode Order"), spOrderCombo.getBorder())); spOrderCombo.setBorder(new CompoundBorder(new TitledBorder("Episode Order"), spOrderCombo.getBorder()));
// fix background issues on OSX
spModeCombo.setOpaque(false);
spLanguageList.setOpaque(false);
spOrderCombo.setOpaque(false);
JPanel message = new JPanel(new MigLayout("fill, flowy, insets 0")); JPanel message = new JPanel(new MigLayout("fill, flowy, insets 0"));
message.add(spLanguageList, "grow"); message.add(spModeCombo, "grow, hmin 24px");
message.add(spLanguageList, "grow, hmin 50px");
message.add(spOrderCombo, "grow, hmin 24px"); message.add(spOrderCombo, "grow, hmin 24px");
JOptionPane pane = new JOptionPane(message, PLAIN_MESSAGE, OK_CANCEL_OPTION); JOptionPane pane = new JOptionPane(message, PLAIN_MESSAGE, OK_CANCEL_OPTION);
pane.createDialog(getWindowAncestor(RenamePanel.this), "Preferences").setVisible(true); pane.createDialog(getWindowAncestor(RenamePanel.this), "Preferences").setVisible(true);
if (pane.getValue() != null && pane.getValue().equals(OK_OPTION)) { if (pane.getValue() != null && pane.getValue().equals(OK_OPTION)) {
persistentPreferredMatchMode.setValue((String) modeCombo.getSelectedItem());
persistentPreferredLanguage.setValue(((Language) languageList.getSelectedValue()).getCode()); persistentPreferredLanguage.setValue(((Language) languageList.getSelectedValue()).getCode());
persistentPreferredEpisodeOrder.setValue(((SortOrder) orderCombo.getSelectedItem()).name()); persistentPreferredEpisodeOrder.setValue(((SortOrder) orderCombo.getSelectedItem()).name());
} }
@ -631,6 +645,7 @@ public class RenamePanel extends JComponent {
renameModel.values().clear(); renameModel.values().clear();
final List<File> remainingFiles = new LinkedList<File>(renameModel.files()); final List<File> remainingFiles = new LinkedList<File>(renameModel.files());
final boolean strict = "strict".equalsIgnoreCase(persistentPreferredMatchMode.getValue());
final SortOrder order = SortOrder.forName(persistentPreferredEpisodeOrder.getValue()); final SortOrder order = SortOrder.forName(persistentPreferredEpisodeOrder.getValue());
final Locale locale = new Locale(persistentPreferredLanguage.getValue()); final Locale locale = new Locale(persistentPreferredLanguage.getValue());
final boolean autodetection = !isShiftOrAltDown(evt); // skip name auto-detection if SHIFT is pressed final boolean autodetection = !isShiftOrAltDown(evt); // skip name auto-detection if SHIFT is pressed
@ -645,7 +660,7 @@ public class RenamePanel extends JComponent {
@Override @Override
protected List<Match<File, ?>> doInBackground() throws Exception { protected List<Match<File, ?>> doInBackground() throws Exception {
List<Match<File, ?>> matches = matcher.match(remainingFiles, order, locale, autodetection, getWindow(RenamePanel.this)); List<Match<File, ?>> matches = matcher.match(remainingFiles, strict, order, locale, autodetection, getWindow(RenamePanel.this));
// remove matched files // remove matched files
for (Match<File, ?> match : matches) { for (Match<File, ?> match : matches) {

View File

@ -37,6 +37,7 @@
^BTN$ ^BTN$
^Cartoon$ ^Cartoon$
^Cinema$ ^Cinema$
^Classic$
^clean$ ^clean$
^cleaned$ ^cleaned$
^Comedy$ ^Comedy$