mirror of
https://github.com/mitb-archive/filebot
synced 2024-12-23 16:28:51 -05:00
* added movie identification support in rename panel
This commit is contained in:
parent
f8f1d5b5b3
commit
3c81eb7f5d
BIN
fw/search.themoviedb.png
Normal file
BIN
fw/search.themoviedb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
BIN
source/net/sourceforge/filebot/resources/search.themoviedb.png
Normal file
BIN
source/net/sourceforge/filebot/resources/search.themoviedb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 695 B |
@ -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<File, ?>> match(List<File> files) throws Exception;
|
||||
}
|
@ -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<List<Match<File, Episode>>, Void> {
|
||||
class EpisodeListMatcher implements AutoCompleteMatcher {
|
||||
|
||||
private final EpisodeListProvider provider;
|
||||
|
||||
private final List<File> files;
|
||||
|
||||
private final SimilarityMetric[] metrics;
|
||||
|
||||
|
||||
public AutoFetchEpisodeListMatcher(EpisodeListProvider provider, Collection<File> files, SimilarityMetric[] metrics) {
|
||||
public EpisodeListMatcher(EpisodeListProvider provider) {
|
||||
this.provider = provider;
|
||||
this.files = new LinkedList<File>(files);
|
||||
this.metrics = metrics.clone();
|
||||
}
|
||||
|
||||
|
||||
public List<File> remainingFiles() {
|
||||
return Collections.unmodifiableList(files);
|
||||
}
|
||||
|
||||
|
||||
@ -171,36 +159,30 @@ class AutoFetchEpisodeListMatcher extends SwingWorker<List<Match<File, Episode>>
|
||||
|
||||
|
||||
@Override
|
||||
protected List<Match<File, Episode>> doInBackground() throws Exception {
|
||||
|
||||
public List<Match<File, ?>> match(final List<File> files) throws Exception {
|
||||
// focus on movie and subtitle files
|
||||
List<File> mediaFiles = FileUtilities.filter(files, VIDEO_FILES, SUBTITLE_FILES);
|
||||
|
||||
// detect series name and fetch episode list
|
||||
Set<Episode> episodes = fetchEpisodeSet(detectSeriesNames(mediaFiles));
|
||||
|
||||
List<Match<File, Episode>> matches = new ArrayList<Match<File, Episode>>();
|
||||
List<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
||||
|
||||
// group by subtitles first and then by files in general
|
||||
for (List<File> filesPerType : mapByFileType(mediaFiles, VIDEO_FILES, SUBTITLE_FILES).values()) {
|
||||
Matcher<File, Episode> matcher = new Matcher<File, Episode>(filesPerType, episodes, metrics);
|
||||
Matcher<File, Episode> matcher = new Matcher<File, Episode>(filesPerType, episodes, MatchSimilarityMetric.defaultSequence());
|
||||
matches.addAll(matcher.match());
|
||||
}
|
||||
|
||||
// restore original order
|
||||
Collections.sort(matches, new Comparator<Match<File, Episode>>() {
|
||||
Collections.sort(matches, new Comparator<Match<File, ?>>() {
|
||||
|
||||
@Override
|
||||
public int compare(Match<File, Episode> o1, Match<File, Episode> o2) {
|
||||
public int compare(Match<File, ?> o1, Match<File, ?> o2) {
|
||||
return files.indexOf(o1.getValue()) - files.indexOf(o2.getValue());
|
||||
}
|
||||
});
|
||||
|
||||
// update remaining files
|
||||
for (Match<File, Episode> match : matches) {
|
||||
files.remove(match.getValue());
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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<File, ?>> match(List<File> 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<Match<File, ?>> matches = new ArrayList<Match<File, ?>>();
|
||||
|
||||
for (int i = 0; i < movieDescriptors.length; i++) {
|
||||
if (movieDescriptors[i] != null) {
|
||||
matches.add(new Match<File, MovieDescriptor>(movieFiles[i], movieDescriptors[i]));
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
}
|
@ -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<List<Match<File, ?>>, Void> worker = new SwingWorker<List<Match<File, ?>>, Void>() {
|
||||
|
||||
private final List<File> remainingFiles = new LinkedList<File>(renameModel.files());
|
||||
|
||||
|
||||
@Override
|
||||
protected List<Match<File, ?>> doInBackground() throws Exception {
|
||||
List<Match<File, ?>> matches = matcher.match(remainingFiles);
|
||||
|
||||
// remove matched files
|
||||
for (Match<File, ?> match : matches) {
|
||||
remainingFiles.remove(match.getValue());
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void done() {
|
||||
try {
|
||||
List<Match<Object, File>> matches = new ArrayList<Match<Object, File>>();
|
||||
|
||||
for (Match<File, Episode> match : get()) {
|
||||
for (Match<File, ?> match : get()) {
|
||||
matches.add(new Match<Object, File>(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 {
|
||||
|
@ -136,11 +136,10 @@ public class AnidbClient implements EpisodeListProvider {
|
||||
List<Episode> episodes = new ArrayList<Episode>(25);
|
||||
|
||||
for (Node node : selectNodes("//ep", dom)) {
|
||||
String flags = getTextContent("flags", 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
|
||||
|
@ -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<MovieDescriptor> searchMovie(String query) throws IOException, SAXException {
|
||||
return getMovies("Movie.search", query);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user