+ 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:
parent
425bfb83ea
commit
097e001111
|
@ -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()) {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>() {
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
^BTN$
|
^BTN$
|
||||||
^Cartoon$
|
^Cartoon$
|
||||||
^Cinema$
|
^Cinema$
|
||||||
|
^Classic$
|
||||||
^clean$
|
^clean$
|
||||||
^cleaned$
|
^cleaned$
|
||||||
^Comedy$
|
^Comedy$
|
||||||
|
|
Loading…
Reference in New Issue