diff --git a/fw/search.themoviedb.png b/fw/search.themoviedb.png new file mode 100644 index 00000000..4df93556 Binary files /dev/null and b/fw/search.themoviedb.png differ diff --git a/source/net/sourceforge/filebot/resources/search.themoviedb.png b/source/net/sourceforge/filebot/resources/search.themoviedb.png new file mode 100644 index 00000000..be69ca65 Binary files /dev/null and b/source/net/sourceforge/filebot/resources/search.themoviedb.png differ diff --git a/source/net/sourceforge/filebot/ui/panel/rename/AutoCompleteMatcher.java b/source/net/sourceforge/filebot/ui/panel/rename/AutoCompleteMatcher.java new file mode 100644 index 00000000..f8d4fca8 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/rename/AutoCompleteMatcher.java @@ -0,0 +1,14 @@ + +package net.sourceforge.filebot.ui.panel.rename; + + +import java.io.File; +import java.util.List; + +import net.sourceforge.filebot.similarity.Match; + + +interface AutoCompleteMatcher { + + List> match(List files) throws Exception; +} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/AutoFetchEpisodeListMatcher.java b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeListMatcher.java similarity index 86% rename from source/net/sourceforge/filebot/ui/panel/rename/AutoFetchEpisodeListMatcher.java rename to source/net/sourceforge/filebot/ui/panel/rename/EpisodeListMatcher.java index 98b81d2d..6af67499 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/AutoFetchEpisodeListMatcher.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/EpisodeListMatcher.java @@ -26,7 +26,6 @@ import java.util.concurrent.RunnableFuture; import javax.swing.Action; import javax.swing.SwingUtilities; -import javax.swing.SwingWorker; import net.sourceforge.filebot.similarity.Match; import net.sourceforge.filebot.similarity.Matcher; @@ -40,24 +39,13 @@ import net.sourceforge.filebot.web.SearchResult; import net.sourceforge.tuned.FileUtilities; -class AutoFetchEpisodeListMatcher extends SwingWorker>, Void> { +class EpisodeListMatcher implements AutoCompleteMatcher { private final EpisodeListProvider provider; - private final List files; - - private final SimilarityMetric[] metrics; - - public AutoFetchEpisodeListMatcher(EpisodeListProvider provider, Collection files, SimilarityMetric[] metrics) { + public EpisodeListMatcher(EpisodeListProvider provider) { this.provider = provider; - this.files = new LinkedList(files); - this.metrics = metrics.clone(); - } - - - public List remainingFiles() { - return Collections.unmodifiableList(files); } @@ -171,36 +159,30 @@ class AutoFetchEpisodeListMatcher extends SwingWorker> @Override - protected List> doInBackground() throws Exception { - + public List> match(final List files) throws Exception { // focus on movie and subtitle files List mediaFiles = FileUtilities.filter(files, VIDEO_FILES, SUBTITLE_FILES); // detect series name and fetch episode list Set episodes = fetchEpisodeSet(detectSeriesNames(mediaFiles)); - List> matches = new ArrayList>(); + List> matches = new ArrayList>(); // group by subtitles first and then by files in general for (List filesPerType : mapByFileType(mediaFiles, VIDEO_FILES, SUBTITLE_FILES).values()) { - Matcher matcher = new Matcher(filesPerType, episodes, metrics); + Matcher matcher = new Matcher(filesPerType, episodes, MatchSimilarityMetric.defaultSequence()); matches.addAll(matcher.match()); } // restore original order - Collections.sort(matches, new Comparator>() { + Collections.sort(matches, new Comparator>() { @Override - public int compare(Match o1, Match o2) { + public int compare(Match o1, Match o2) { return files.indexOf(o1.getValue()) - files.indexOf(o2.getValue()); } }); - // update remaining files - for (Match match : matches) { - files.remove(match.getValue()); - } - return matches; } diff --git a/source/net/sourceforge/filebot/ui/panel/rename/MovieFormatter.java b/source/net/sourceforge/filebot/ui/panel/rename/MovieFormatter.java new file mode 100644 index 00000000..b8e52bf4 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/rename/MovieFormatter.java @@ -0,0 +1,29 @@ + +package net.sourceforge.filebot.ui.panel.rename; + + +import net.sourceforge.filebot.similarity.Match; +import net.sourceforge.filebot.web.MovieDescriptor; + + +class MovieFormatter implements MatchFormatter { + + @Override + public boolean canFormat(Match match) { + return match.getValue() instanceof MovieDescriptor; + } + + + @Override + public String preview(Match match) { + return format(match); + } + + + @Override + public String format(Match match) { + MovieDescriptor movie = (MovieDescriptor) match.getValue(); + return movie.getName(); + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/MovieHashMatcher.java b/source/net/sourceforge/filebot/ui/panel/rename/MovieHashMatcher.java new file mode 100644 index 00000000..09f54106 --- /dev/null +++ b/source/net/sourceforge/filebot/ui/panel/rename/MovieHashMatcher.java @@ -0,0 +1,44 @@ + +package net.sourceforge.filebot.ui.panel.rename; + + +import static net.sourceforge.filebot.MediaTypes.*; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import net.sourceforge.filebot.similarity.Match; +import net.sourceforge.filebot.web.MovieDescriptor; +import net.sourceforge.filebot.web.MovieIdentificationService; +import net.sourceforge.tuned.FileUtilities; + + +class MovieHashMatcher implements AutoCompleteMatcher { + + private final MovieIdentificationService service; + + + public MovieHashMatcher(MovieIdentificationService service) { + this.service = service; + } + + + @Override + public List> match(List files) throws Exception { + // focus on movie and subtitle files + File[] movieFiles = FileUtilities.filter(files, VIDEO_FILES).toArray(new File[0]); + MovieDescriptor[] movieDescriptors = service.getMovieDescriptors(movieFiles); + + List> matches = new ArrayList>(); + + for (int i = 0; i < movieDescriptors.length; i++) { + if (movieDescriptors[i] != null) { + matches.add(new Match(movieFiles[i], movieDescriptors[i])); + } + } + + return matches; + } + +} diff --git a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java index 50c0e9d6..ef375478 100644 --- a/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java +++ b/source/net/sourceforge/filebot/ui/panel/rename/RenamePanel.java @@ -12,6 +12,8 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -24,6 +26,7 @@ import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; import ca.odell.glazedlists.ListSelection; import ca.odell.glazedlists.swing.EventSelectionModel; @@ -37,6 +40,8 @@ import net.sourceforge.filebot.web.AnidbClient; import net.sourceforge.filebot.web.Episode; import net.sourceforge.filebot.web.EpisodeListProvider; import net.sourceforge.filebot.web.IMDbClient; +import net.sourceforge.filebot.web.MovieDescriptor; +import net.sourceforge.filebot.web.TMDbClient; import net.sourceforge.filebot.web.TVDotComClient; import net.sourceforge.filebot.web.TVRageClient; import net.sourceforge.filebot.web.TheTVDBClient; @@ -75,6 +80,9 @@ public class RenamePanel extends JComponent { // filename formatter renameModel.useFormatter(File.class, new FileNameFormatter(renameModel.preserveExtension())); + // movie formatter + renameModel.useFormatter(MovieDescriptor.class, new MovieFormatter()); + try { // restore custom episode formatter renameModel.useFormatter(Episode.class, new EpisodeExpressionFormatter(persistentFormatExpression.getValue())); @@ -135,12 +143,17 @@ public class RenamePanel extends JComponent { protected ActionPopup createFetchPopup() { final ActionPopup actionPopup = new ActionPopup("Fetch Episode List", ResourceManager.getIcon("action.fetch")); - // create actions for match popup - actionPopup.add(new AutoFetchEpisodeListAction(new TVRageClient())); - actionPopup.add(new AutoFetchEpisodeListAction(new AnidbClient())); - actionPopup.add(new AutoFetchEpisodeListAction(new TVDotComClient())); - actionPopup.add(new AutoFetchEpisodeListAction(new IMDbClient())); - actionPopup.add(new AutoFetchEpisodeListAction(new TheTVDBClient(getApplicationProperty("thetvdb.apikey")))); + // create actions for match popup episode list completion + for (EpisodeListProvider provider : Arrays.asList(new TVRageClient(), new AnidbClient(), new TVDotComClient(), new IMDbClient(), new TheTVDBClient(getApplicationProperty("thetvdb.apikey")))) { + actionPopup.add(new AutoCompleteAction(provider.getName(), provider.getIcon(), new EpisodeListMatcher(provider))); + } + + actionPopup.addSeparator(); + actionPopup.addDescription(new JLabel("Movie Identification:")); + + // create action for movie name completion + TMDbClient tmdb = new TMDbClient(getApplicationProperty("themoviedb.apikey")); + actionPopup.add(new AutoCompleteAction(tmdb.getName(), tmdb.getIcon(), new MovieHashMatcher(tmdb))); actionPopup.addSeparator(); actionPopup.addDescription(new JLabel("Options:")); @@ -247,15 +260,15 @@ public class RenamePanel extends JComponent { } - protected class AutoFetchEpisodeListAction extends AbstractAction { + protected class AutoCompleteAction extends AbstractAction { - private final EpisodeListProvider provider; + private final AutoCompleteMatcher matcher; - public AutoFetchEpisodeListAction(EpisodeListProvider provider) { - super(provider.getName(), provider.getIcon()); + public AutoCompleteAction(String name, Icon icon, AutoCompleteMatcher matcher) { + super(name, icon); - this.provider = provider; + this.matcher = matcher; // disable action while episode list matcher is working namesList.addPropertyChangeListener(LOADING_PROPERTY, new PropertyChangeListener() { @@ -277,23 +290,38 @@ public class RenamePanel extends JComponent { // clear names list renameModel.values().clear(); - AutoFetchEpisodeListMatcher worker = new AutoFetchEpisodeListMatcher(provider, renameModel.files(), MatchSimilarityMetric.defaultSequence()) { + SwingWorker>, Void> worker = new SwingWorker>, Void>() { + private final List remainingFiles = new LinkedList(renameModel.files()); + + + @Override + protected List> doInBackground() throws Exception { + List> matches = matcher.match(remainingFiles); + + // remove matched files + for (Match match : matches) { + remainingFiles.remove(match.getValue()); + } + + return matches; + } + + @Override protected void done() { try { List> matches = new ArrayList>(); - for (Match match : get()) { + for (Match match : get()) { matches.add(new Match(match.getCandidate(), match.getValue())); } renameModel.clear(); - renameModel.addAll(matches); // add remaining file entries - renameModel.files().addAll(remainingFiles()); + renameModel.files().addAll(remainingFiles); } catch (Exception e) { Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e); } finally { diff --git a/source/net/sourceforge/filebot/web/AnidbClient.java b/source/net/sourceforge/filebot/web/AnidbClient.java index 4cb5e5ce..73aff807 100644 --- a/source/net/sourceforge/filebot/web/AnidbClient.java +++ b/source/net/sourceforge/filebot/web/AnidbClient.java @@ -136,11 +136,10 @@ public class AnidbClient implements EpisodeListProvider { List episodes = new ArrayList(25); for (Node node : selectNodes("//ep", dom)) { - String flags = getTextContent("flags", node); + String number = getTextContent("epno", node); - // allow only normal and recap episodes - if (flags == null || flags.equals("2")) { - String number = getTextContent("epno", node); + // ignore special episodes + if (number != null && number.matches("\\d+")) { String title = selectString(".//title[@lang='en']", node); // no seasons for anime diff --git a/source/net/sourceforge/filebot/web/TMDbClient.java b/source/net/sourceforge/filebot/web/TMDbClient.java index 556b0050..63c23dd6 100644 --- a/source/net/sourceforge/filebot/web/TMDbClient.java +++ b/source/net/sourceforge/filebot/web/TMDbClient.java @@ -13,9 +13,13 @@ import java.util.ArrayList; import java.util.List; import java.util.Scanner; +import javax.swing.Icon; + import org.w3c.dom.Node; import org.xml.sax.SAXException; +import net.sourceforge.filebot.ResourceManager; + public class TMDbClient implements MovieIdentificationService { @@ -30,6 +34,16 @@ public class TMDbClient implements MovieIdentificationService { } + public String getName() { + return "TheMovieDB"; + } + + + public Icon getIcon() { + return ResourceManager.getIcon("search.themoviedb"); + } + + public List searchMovie(String query) throws IOException, SAXException { return getMovies("Movie.search", query); }