* 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:
Reinhard Pointner 2011-08-22 03:43:22 +00:00
parent 8ab04ba075
commit 379f0a9cc1
8 changed files with 88 additions and 37 deletions

View File

@ -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;
} }

View File

@ -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, ?>>();

View 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);
} }
} }

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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());
} }