mirror of
https://github.com/mitb-archive/filebot
synced 2025-01-11 05:48:01 -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.Action;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
import javax.swing.SwingWorker;
|
|
||||||
|
|
||||||
import net.sourceforge.filebot.similarity.Match;
|
import net.sourceforge.filebot.similarity.Match;
|
||||||
import net.sourceforge.filebot.similarity.Matcher;
|
import net.sourceforge.filebot.similarity.Matcher;
|
||||||
@ -40,24 +39,13 @@ import net.sourceforge.filebot.web.SearchResult;
|
|||||||
import net.sourceforge.tuned.FileUtilities;
|
import net.sourceforge.tuned.FileUtilities;
|
||||||
|
|
||||||
|
|
||||||
class AutoFetchEpisodeListMatcher extends SwingWorker<List<Match<File, Episode>>, Void> {
|
class EpisodeListMatcher implements AutoCompleteMatcher {
|
||||||
|
|
||||||
private final EpisodeListProvider provider;
|
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.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
|
@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
|
// 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));
|
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
|
// group by subtitles first and then by files in general
|
||||||
for (List<File> filesPerType : mapByFileType(mediaFiles, VIDEO_FILES, SUBTITLE_FILES).values()) {
|
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());
|
matches.addAll(matcher.match());
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore original order
|
// restore original order
|
||||||
Collections.sort(matches, new Comparator<Match<File, Episode>>() {
|
Collections.sort(matches, new Comparator<Match<File, ?>>() {
|
||||||
|
|
||||||
@Override
|
@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());
|
return files.indexOf(o1.getValue()) - files.indexOf(o2.getValue());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// update remaining files
|
|
||||||
for (Match<File, Episode> match : matches) {
|
|
||||||
files.remove(match.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
return matches;
|
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.beans.PropertyChangeListener;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
@ -24,6 +26,7 @@ import javax.swing.JComponent;
|
|||||||
import javax.swing.JLabel;
|
import javax.swing.JLabel;
|
||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
import javax.swing.SwingUtilities;
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.SwingWorker;
|
||||||
|
|
||||||
import ca.odell.glazedlists.ListSelection;
|
import ca.odell.glazedlists.ListSelection;
|
||||||
import ca.odell.glazedlists.swing.EventSelectionModel;
|
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.Episode;
|
||||||
import net.sourceforge.filebot.web.EpisodeListProvider;
|
import net.sourceforge.filebot.web.EpisodeListProvider;
|
||||||
import net.sourceforge.filebot.web.IMDbClient;
|
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.TVDotComClient;
|
||||||
import net.sourceforge.filebot.web.TVRageClient;
|
import net.sourceforge.filebot.web.TVRageClient;
|
||||||
import net.sourceforge.filebot.web.TheTVDBClient;
|
import net.sourceforge.filebot.web.TheTVDBClient;
|
||||||
@ -75,6 +80,9 @@ public class RenamePanel extends JComponent {
|
|||||||
// filename formatter
|
// filename formatter
|
||||||
renameModel.useFormatter(File.class, new FileNameFormatter(renameModel.preserveExtension()));
|
renameModel.useFormatter(File.class, new FileNameFormatter(renameModel.preserveExtension()));
|
||||||
|
|
||||||
|
// movie formatter
|
||||||
|
renameModel.useFormatter(MovieDescriptor.class, new MovieFormatter());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// restore custom episode formatter
|
// restore custom episode formatter
|
||||||
renameModel.useFormatter(Episode.class, new EpisodeExpressionFormatter(persistentFormatExpression.getValue()));
|
renameModel.useFormatter(Episode.class, new EpisodeExpressionFormatter(persistentFormatExpression.getValue()));
|
||||||
@ -135,12 +143,17 @@ public class RenamePanel extends JComponent {
|
|||||||
protected ActionPopup createFetchPopup() {
|
protected ActionPopup createFetchPopup() {
|
||||||
final ActionPopup actionPopup = new ActionPopup("Fetch Episode List", ResourceManager.getIcon("action.fetch"));
|
final ActionPopup actionPopup = new ActionPopup("Fetch Episode List", ResourceManager.getIcon("action.fetch"));
|
||||||
|
|
||||||
// create actions for match popup
|
// create actions for match popup episode list completion
|
||||||
actionPopup.add(new AutoFetchEpisodeListAction(new TVRageClient()));
|
for (EpisodeListProvider provider : Arrays.asList(new TVRageClient(), new AnidbClient(), new TVDotComClient(), new IMDbClient(), new TheTVDBClient(getApplicationProperty("thetvdb.apikey")))) {
|
||||||
actionPopup.add(new AutoFetchEpisodeListAction(new AnidbClient()));
|
actionPopup.add(new AutoCompleteAction(provider.getName(), provider.getIcon(), new EpisodeListMatcher(provider)));
|
||||||
actionPopup.add(new AutoFetchEpisodeListAction(new TVDotComClient()));
|
}
|
||||||
actionPopup.add(new AutoFetchEpisodeListAction(new IMDbClient()));
|
|
||||||
actionPopup.add(new AutoFetchEpisodeListAction(new TheTVDBClient(getApplicationProperty("thetvdb.apikey"))));
|
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.addSeparator();
|
||||||
actionPopup.addDescription(new JLabel("Options:"));
|
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) {
|
public AutoCompleteAction(String name, Icon icon, AutoCompleteMatcher matcher) {
|
||||||
super(provider.getName(), provider.getIcon());
|
super(name, icon);
|
||||||
|
|
||||||
this.provider = provider;
|
this.matcher = matcher;
|
||||||
|
|
||||||
// disable action while episode list matcher is working
|
// disable action while episode list matcher is working
|
||||||
namesList.addPropertyChangeListener(LOADING_PROPERTY, new PropertyChangeListener() {
|
namesList.addPropertyChangeListener(LOADING_PROPERTY, new PropertyChangeListener() {
|
||||||
@ -277,23 +290,38 @@ public class RenamePanel extends JComponent {
|
|||||||
// clear names list
|
// clear names list
|
||||||
renameModel.values().clear();
|
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
|
@Override
|
||||||
protected void done() {
|
protected void done() {
|
||||||
try {
|
try {
|
||||||
List<Match<Object, File>> matches = new ArrayList<Match<Object, File>>();
|
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()));
|
matches.add(new Match<Object, File>(match.getCandidate(), match.getValue()));
|
||||||
}
|
}
|
||||||
|
|
||||||
renameModel.clear();
|
renameModel.clear();
|
||||||
|
|
||||||
renameModel.addAll(matches);
|
renameModel.addAll(matches);
|
||||||
|
|
||||||
// add remaining file entries
|
// add remaining file entries
|
||||||
renameModel.files().addAll(remainingFiles());
|
renameModel.files().addAll(remainingFiles);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
Logger.getLogger("ui").log(Level.WARNING, ExceptionUtilities.getRootCauseMessage(e), e);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -136,11 +136,10 @@ public class AnidbClient implements EpisodeListProvider {
|
|||||||
List<Episode> episodes = new ArrayList<Episode>(25);
|
List<Episode> episodes = new ArrayList<Episode>(25);
|
||||||
|
|
||||||
for (Node node : selectNodes("//ep", dom)) {
|
for (Node node : selectNodes("//ep", dom)) {
|
||||||
String flags = getTextContent("flags", node);
|
String number = getTextContent("epno", node);
|
||||||
|
|
||||||
// allow only normal and recap episodes
|
// ignore special episodes
|
||||||
if (flags == null || flags.equals("2")) {
|
if (number != null && number.matches("\\d+")) {
|
||||||
String number = getTextContent("epno", node);
|
|
||||||
String title = selectString(".//title[@lang='en']", node);
|
String title = selectString(".//title[@lang='en']", node);
|
||||||
|
|
||||||
// no seasons for anime
|
// no seasons for anime
|
||||||
|
@ -13,9 +13,13 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
import javax.swing.Icon;
|
||||||
|
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
|
|
||||||
|
import net.sourceforge.filebot.ResourceManager;
|
||||||
|
|
||||||
|
|
||||||
public class TMDbClient implements MovieIdentificationService {
|
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 {
|
public List<MovieDescriptor> searchMovie(String query) throws IOException, SAXException {
|
||||||
return getMovies("Movie.search", query);
|
return getMovies("Movie.search", query);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user