* support manual input of movie/series title (as fallback if auto-detection fails or if forced via SHIFT-clicking the data source)
* fixed movie mode issues (osdb title/year parsing problem, nfo file imdbid parser problem)
This commit is contained in:
parent
8ab04ba075
commit
379f0a9cc1
|
@ -11,5 +11,5 @@ import net.sourceforge.filebot.similarity.Match;
|
||||||
|
|
||||||
interface AutoCompleteMatcher {
|
interface AutoCompleteMatcher {
|
||||||
|
|
||||||
List<Match<File, ?>> match(List<File> files, Locale locale) throws Exception;
|
List<Match<File, ?>> match(List<File> files, Locale locale, boolean autodetection) throws Exception;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
package net.sourceforge.filebot.ui.panel.rename;
|
package net.sourceforge.filebot.ui.panel.rename;
|
||||||
|
|
||||||
|
|
||||||
|
import static java.util.Collections.*;
|
||||||
|
import static javax.swing.JOptionPane.*;
|
||||||
import static net.sourceforge.filebot.MediaTypes.*;
|
import static net.sourceforge.filebot.MediaTypes.*;
|
||||||
import static net.sourceforge.tuned.FileUtilities.*;
|
import static net.sourceforge.tuned.FileUtilities.*;
|
||||||
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
||||||
|
@ -48,12 +50,23 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Collection<String> detectSeriesNames(Collection<File> files) {
|
protected Collection<String> grabSeriesNames(Collection<File> files, boolean autodetect) {
|
||||||
// detect series name(s) from files
|
Collection<String> names = null;
|
||||||
Collection<String> names = new SeriesNameMatcher().matchAll(files.toArray(new File[0]));
|
|
||||||
|
|
||||||
if (names.isEmpty())
|
// auto-detect series name(s) from files
|
||||||
throw new IllegalArgumentException("Cannot determine series name.");
|
if (autodetect) {
|
||||||
|
names = new SeriesNameMatcher().matchAll(files.toArray(new File[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// require user input if auto-detection fails
|
||||||
|
if (names == null || names.isEmpty()) {
|
||||||
|
String suggestion = new SeriesNameMatcher().matchBySeasonEpisodePattern(getName(files.iterator().next()));
|
||||||
|
String input = showInputDialog(null, "Enter series name:", suggestion);
|
||||||
|
|
||||||
|
if (input != null) {
|
||||||
|
names = singleton(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
@ -159,12 +172,12 @@ class EpisodeListMatcher implements AutoCompleteMatcher {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Match<File, ?>> match(final List<File> files, Locale locale) throws Exception {
|
public List<Match<File, ?>> match(final List<File> files, Locale locale, boolean autodetection) throws Exception {
|
||||||
// focus on movie and subtitle files
|
// focus on movie and subtitle files
|
||||||
List<File> mediaFiles = FileUtilities.filter(files, VIDEO_FILES, SUBTITLE_FILES);
|
List<File> mediaFiles = FileUtilities.filter(files, VIDEO_FILES, SUBTITLE_FILES);
|
||||||
|
|
||||||
// detect series name and fetch episode list
|
// detect series name and fetch episode list
|
||||||
Set<Episode> episodes = fetchEpisodeSet(detectSeriesNames(mediaFiles), locale);
|
Set<Episode> episodes = fetchEpisodeSet(grabSeriesNames(mediaFiles, autodetection), locale);
|
||||||
|
|
||||||
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
package net.sourceforge.filebot.ui.panel.rename;
|
package net.sourceforge.filebot.ui.panel.rename;
|
||||||
|
|
||||||
|
|
||||||
|
import static javax.swing.JOptionPane.*;
|
||||||
import static net.sourceforge.filebot.MediaTypes.*;
|
import static net.sourceforge.filebot.MediaTypes.*;
|
||||||
import static net.sourceforge.tuned.FileUtilities.*;
|
import static net.sourceforge.tuned.FileUtilities.*;
|
||||||
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
import static net.sourceforge.tuned.ui.TunedUtilities.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -45,21 +47,26 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Match<File, ?>> match(final List<File> files, Locale locale) throws Exception {
|
public List<Match<File, ?>> match(final List<File> files, Locale locale, boolean autodetect) 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]);
|
||||||
MovieDescriptor[] movieDescriptors = service.getMovieDescriptors(movieFiles, locale);
|
MovieDescriptor[] movieByFileHash = new MovieDescriptor[movieFiles.length];
|
||||||
|
|
||||||
|
// match movie hashes online
|
||||||
|
if (autodetect) {
|
||||||
|
movieByFileHash = service.getMovieDescriptors(movieFiles, locale);
|
||||||
|
}
|
||||||
|
|
||||||
// map movies to (possibly multiple) files (in natural order)
|
// map movies to (possibly multiple) files (in natural order)
|
||||||
Map<MovieDescriptor, SortedSet<File>> filesByMovie = new HashMap<MovieDescriptor, SortedSet<File>>();
|
Map<MovieDescriptor, SortedSet<File>> filesByMovie = new HashMap<MovieDescriptor, SortedSet<File>>();
|
||||||
|
|
||||||
// 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++) {
|
||||||
MovieDescriptor movie = movieDescriptors[i];
|
MovieDescriptor movie = movieByFileHash[i];
|
||||||
|
|
||||||
// unknown hash, try via imdb id from nfo file
|
// unknown hash, try via imdb id from nfo file
|
||||||
if (movie == null) {
|
if (movie == null) {
|
||||||
movie = determineMovie(movieFiles[i], locale);
|
movie = grabMovieName(movieFiles[i], locale, autodetect);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if we managed to lookup the movie descriptor
|
// check if we managed to lookup the movie descriptor
|
||||||
|
@ -119,17 +126,17 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Set<Integer> grepImdbId(File... files) throws IOException {
|
private Set<Integer> grepImdbId(File... files) throws IOException {
|
||||||
Set<Integer> collection = new HashSet<Integer>();
|
Set<Integer> collection = new HashSet<Integer>();
|
||||||
|
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
Scanner scanner = new Scanner(file);
|
Scanner scanner = new Scanner(new FileInputStream(file));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// scan for imdb id patterns like tt1234567
|
// scan for imdb id patterns like tt1234567
|
||||||
String imdb = null;
|
String imdb = null;
|
||||||
|
|
||||||
while ((imdb = scanner.findWithinHorizon("(?<=tt)\\d{7}", 32 * 1024)) != null) {
|
while ((imdb = scanner.findWithinHorizon("(?<=tt)\\d{7}", 64 * 1024)) != null) {
|
||||||
collection.add(Integer.parseInt(imdb));
|
collection.add(Integer.parseInt(imdb));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -141,7 +148,12 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected MovieDescriptor determineMovie(File movieFile, Locale locale) throws Exception {
|
private String normalizeMovieName(File movie) {
|
||||||
|
return getName(movie).replaceAll("\\p{Punct}+", " ").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected MovieDescriptor grabMovieName(File movieFile, Locale locale, boolean autodetect) throws Exception {
|
||||||
List<MovieDescriptor> options = new ArrayList<MovieDescriptor>();
|
List<MovieDescriptor> options = new ArrayList<MovieDescriptor>();
|
||||||
|
|
||||||
// try to grep imdb id from nfo files
|
// try to grep imdb id from nfo files
|
||||||
|
@ -153,15 +165,20 @@ class MovieHashMatcher implements AutoCompleteMatcher {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// search by file name
|
// search by file name or folder name
|
||||||
if (options.isEmpty()) {
|
for (File it : new File[] { movieFile, movieFile.getParentFile() }) {
|
||||||
String query = getName(movieFile).replaceAll("\\p{Punct}+", " ").trim();
|
if (autodetect && options.isEmpty()) {
|
||||||
options = service.searchMovie(query, locale);
|
options = service.searchMovie(normalizeMovieName(it), locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// allow manual user input
|
||||||
|
if (options.isEmpty() || !autodetect) {
|
||||||
|
String suggestion = options.isEmpty() ? normalizeMovieName(movieFile) : options.get(0).getName();
|
||||||
|
String input = showInputDialog(null, "Enter movie name:", suggestion);
|
||||||
|
|
||||||
// search by folder name
|
if (input != null) {
|
||||||
if (options.isEmpty()) {
|
options = service.searchMovie(input, locale);
|
||||||
query = getName(movieFile.getParentFile()).replaceAll("\\p{Punct}+", " ").trim();
|
|
||||||
options = service.searchMovie(query, locale);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -327,7 +327,7 @@ public class RenamePanel extends JComponent {
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent evt) {
|
public void actionPerformed(final ActionEvent evt) {
|
||||||
// auto-match in progress
|
// auto-match in progress
|
||||||
namesList.firePropertyChange(LOADING_PROPERTY, false, true);
|
namesList.firePropertyChange(LOADING_PROPERTY, false, true);
|
||||||
|
|
||||||
|
@ -338,11 +338,12 @@ public class RenamePanel extends JComponent {
|
||||||
|
|
||||||
private final List<File> remainingFiles = new LinkedList<File>(renameModel.files());
|
private final List<File> remainingFiles = new LinkedList<File>(renameModel.files());
|
||||||
private final Locale locale = new Locale(persistentPreferredLanguage.getValue());
|
private final Locale locale = new Locale(persistentPreferredLanguage.getValue());
|
||||||
|
private final boolean autodetection = !isShiftDown(evt); // skip name auto-detection if SHIFT is pressed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected List<Match<File, ?>> doInBackground() throws Exception {
|
protected List<Match<File, ?>> doInBackground() throws Exception {
|
||||||
List<Match<File, ?>> matches = matcher.match(remainingFiles, locale);
|
List<Match<File, ?>> matches = matcher.match(remainingFiles, locale, autodetection);
|
||||||
|
|
||||||
// remove matched files
|
// remove matched files
|
||||||
for (Match<File, ?> match : matches) {
|
for (Match<File, ?> match : matches) {
|
||||||
|
|
|
@ -11,11 +11,6 @@ public class MovieDescriptor extends SearchResult {
|
||||||
private final int imdbId;
|
private final int imdbId;
|
||||||
|
|
||||||
|
|
||||||
public MovieDescriptor(String name, int imdbId) {
|
|
||||||
this(name, -1, imdbId);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public MovieDescriptor(String name, int year, int imdbId) {
|
public MovieDescriptor(String name, int year, int imdbId) {
|
||||||
super(name);
|
super(name);
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,10 @@ import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.DeflaterInputStream;
|
import java.util.zip.DeflaterInputStream;
|
||||||
|
|
||||||
import redstone.xmlrpc.XmlRpcClient;
|
import redstone.xmlrpc.XmlRpcClient;
|
||||||
|
@ -123,11 +127,22 @@ public class OpenSubtitlesXmlRpc {
|
||||||
List<Map<String, String>> movieData = (List<Map<String, String>>) response.get("data");
|
List<Map<String, String>> movieData = (List<Map<String, String>>) response.get("data");
|
||||||
List<MovieDescriptor> movies = new ArrayList<MovieDescriptor>();
|
List<MovieDescriptor> movies = new ArrayList<MovieDescriptor>();
|
||||||
|
|
||||||
|
// title pattern
|
||||||
|
Pattern pattern = Pattern.compile("(.+)[(](\\d{4})[)]");
|
||||||
|
|
||||||
for (Map<String, String> movie : movieData) {
|
for (Map<String, String> movie : movieData) {
|
||||||
// get non-aka title (aka titles were separated by Â, and then aka later on)
|
// match movie name and movie year from search result
|
||||||
Scanner titleScanner = new Scanner(movie.get("title")).useDelimiter("(\u00C2)|(\\s+aka\\s+)");
|
Matcher matcher = pattern.matcher(movie.get("title"));
|
||||||
|
|
||||||
movies.add(new MovieDescriptor(titleScanner.next().trim(), Integer.parseInt(movie.get("id"))));
|
if (matcher.find()) {
|
||||||
|
String name = matcher.group(1).trim();
|
||||||
|
int year = Integer.parseInt(matcher.group(2));
|
||||||
|
int imdbid = Integer.parseInt(movie.get("id"));
|
||||||
|
|
||||||
|
movies.add(new MovieDescriptor(name, year, imdbid));
|
||||||
|
} else {
|
||||||
|
Logger.getLogger(OpenSubtitlesXmlRpc.class.getName()).log(Level.WARNING, "Error parsing title: " + movie);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return movies;
|
return movies;
|
||||||
|
|
|
@ -49,6 +49,16 @@ public final class TunedUtilities {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean isShiftDown(ActionEvent evt) {
|
||||||
|
return checkModifiers(evt.getModifiers(), ActionEvent.SHIFT_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean checkModifiers(int modifiers, int mask) {
|
||||||
|
return ((modifiers & mask) == mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static JButton createImageButton(Action action) {
|
public static JButton createImageButton(Action action) {
|
||||||
JButton button = new JButton(action);
|
JButton button = new JButton(action);
|
||||||
button.setHideActionText(true);
|
button.setHideActionText(true);
|
||||||
|
|
|
@ -35,11 +35,11 @@ public class OpenSubtitlesXmlRpcTest {
|
||||||
@Test
|
@Test
|
||||||
public void search() throws Exception {
|
public void search() throws Exception {
|
||||||
List<MovieDescriptor> list = xmlrpc.searchMoviesOnIMDB("babylon 5");
|
List<MovieDescriptor> list = xmlrpc.searchMoviesOnIMDB("babylon 5");
|
||||||
|
|
||||||
MovieDescriptor sample = (MovieDescriptor) list.get(0);
|
MovieDescriptor sample = (MovieDescriptor) list.get(0);
|
||||||
|
|
||||||
// check sample entry
|
// check sample entry
|
||||||
assertEquals("\"Babylon 5\" (1994)", sample.getName());
|
assertEquals("\"Babylon 5\"", sample.getName());
|
||||||
|
assertEquals(1994, sample.getYear());
|
||||||
assertEquals(105946, sample.getImdbId());
|
assertEquals(105946, sample.getImdbId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue