diff --git a/source/net/sourceforge/filebot/cli/CmdlineOperations.java b/source/net/sourceforge/filebot/cli/CmdlineOperations.java index d43cd94b..14799afa 100644 --- a/source/net/sourceforge/filebot/cli/CmdlineOperations.java +++ b/source/net/sourceforge/filebot/cli/CmdlineOperations.java @@ -19,6 +19,7 @@ import java.io.FileFilter; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; +import java.util.AbstractMap; import java.util.AbstractMap.SimpleImmutableEntry; import java.util.ArrayList; import java.util.Collection; @@ -153,7 +154,7 @@ public class CmdlineOperations implements CmdlineInterface { } // similarity metrics for matching - List> matches = new ArrayList>(); + List> matches = new ArrayList>(); // auto-determine optimal batch sets for (Entry, Set> sameSeriesGroup : mapSeriesNamesByFiles(mediaFiles, locale).entrySet()) { @@ -188,7 +189,7 @@ public class CmdlineOperations implements CmdlineInterface { CLILogger.fine(String.format("Apply Filter: {%s}", filter.getExpression())); for (Iterator itr = episodes.iterator(); itr.hasNext();) { Episode episode = itr.next(); - if (filter.matches(new MediaBindingBean(episode, null))) { + if (filter.matches(new MediaBindingBean(episode, null, null))) { CLILogger.finest(String.format("Include [%s]", episode)); } else { itr.remove(); @@ -221,10 +222,10 @@ public class CmdlineOperations implements CmdlineInterface { // map old files to new paths by applying formatting and validating filenames Map renameMap = new LinkedHashMap(); - for (Match match : matches) { + for (Match match : matches) { File file = match.getValue(); Object episode = match.getCandidate(); - String newName = (format != null) ? format.format(new MediaBindingBean(episode, file)) : validateFileName(EpisodeFormat.SeasonEpisode.format(episode)); + String newName = (format != null) ? format.format(new MediaBindingBean(episode, file, getContext(matches))) : validateFileName(EpisodeFormat.SeasonEpisode.format(episode)); renameMap.put(file, getDestinationFile(file, newName, outputDir)); } @@ -494,7 +495,7 @@ public class CmdlineOperations implements CmdlineInterface { for (Match match : matches) { File file = match.getValue(); Object movie = match.getCandidate(); - String newName = (format != null) ? format.format(new MediaBindingBean(movie, file)) : validateFileName(MovieFormat.NameYear.format(movie)); + String newName = (format != null) ? format.format(new MediaBindingBean(movie, file, getContext(matches))) : validateFileName(MovieFormat.NameYear.format(movie)); renameMap.put(file, getDestinationFile(file, newName, outputDir)); } @@ -509,17 +510,21 @@ public class CmdlineOperations implements CmdlineInterface { CLILogger.config(format("Rename music using [%s]", service.getName())); List audioFiles = filter(files, AUDIO_FILES); + // check audio files against acoustid + List> matches = new ArrayList>(); + for (Entry it : service.lookup(audioFiles).entrySet()) { + if (it.getKey() != null && it.getValue() != null) { + matches.add(new Match(it.getKey(), it.getValue())); + } + } + // map old files to new paths by applying formatting and validating filenames Map renameMap = new LinkedHashMap(); - // check audio files against acoustid - for (Entry match : service.lookup(audioFiles).entrySet()) { - File file = match.getKey(); - AudioTrack music = match.getValue(); - if (music == null) - continue; - - String newName = (format != null) ? format.format(new MediaBindingBean(music, file)) : validateFileName(music.toString()); + for (Match it : matches) { + File file = it.getValue(); + AudioTrack music = (AudioTrack) it.getCandidate(); + String newName = (format != null) ? format.format(new MediaBindingBean(music, file, getContext(matches))) : validateFileName(music.toString()); renameMap.put(file, getDestinationFile(file, newName, outputDir)); } @@ -539,6 +544,23 @@ public class CmdlineOperations implements CmdlineInterface { } + private Map getContext(final Collection> matches) { + return new AbstractMap() { + + @Override + public Set> entrySet() { + Set> context = new LinkedHashSet>(); + for (Match it : matches) { + if (it.getValue() != null && it.getCandidate() != null) { + context.add(new SimpleImmutableEntry(it.getValue(), it.getCandidate())); + } + } + return context; + } + }; + } + + private File getDestinationFile(File original, String newName, File outputDir) { String extension = getExtension(original); File newFile = new File(extension != null ? newName + '.' + extension : newName); @@ -1028,7 +1050,7 @@ public class CmdlineOperations implements CmdlineInterface { List episodes = new ArrayList(); for (Episode it : service.getEpisodeList(hit, sortOrder, locale)) { - String name = (format != null) ? format.format(new MediaBindingBean(it, null)) : EpisodeFormat.SeasonEpisode.format(it); + String name = (format != null) ? format.format(new MediaBindingBean(it, null, null)) : EpisodeFormat.SeasonEpisode.format(it); episodes.add(name); } @@ -1039,7 +1061,7 @@ public class CmdlineOperations implements CmdlineInterface { @Override public String getMediaInfo(File file, String expression) throws Exception { ExpressionFormat format = new ExpressionFormat(expression != null ? expression : "{fn} [{resolution} {af} {vc} {ac}]"); - return format.format(new MediaBindingBean(file, file)); + return format.format(new MediaBindingBean(file, file, null)); } diff --git a/source/net/sourceforge/filebot/format/MediaBindingBean.java b/source/net/sourceforge/filebot/format/MediaBindingBean.java index 7294e910..d71108d7 100644 --- a/source/net/sourceforge/filebot/format/MediaBindingBean.java +++ b/source/net/sourceforge/filebot/format/MediaBindingBean.java @@ -21,6 +21,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Scanner; import java.util.Set; import java.util.SortedSet; @@ -46,13 +47,16 @@ public class MediaBindingBean { private final Object infoObject; private final File mediaFile; + private final Map context; + private MediaInfo mediaInfo; private Object metaInfo; - public MediaBindingBean(Object infoObject, File mediaFile) { + public MediaBindingBean(Object infoObject, File mediaFile, Map context) { this.infoObject = infoObject; this.mediaFile = mediaFile; + this.context = context; } @@ -597,7 +601,7 @@ public class MediaBindingBean { @Define("folder") public File getMediaParentFolder() { - return mediaFile.getParentFile(); + return getMediaFile().getParentFile(); } @@ -607,11 +611,24 @@ public class MediaBindingBean { } + @Define("object") public Object getInfoObject() { return infoObject; } + @Define("index") + public Integer getIndex() { + return new ArrayList(getContext().keySet()).indexOf(getMediaFile()) + 1; + } + + + @Define("model") + public Map getContext() { + return context; + } + + private File getInferredMediaFile() { // make sure media file is defined checkMediaFile(); @@ -622,7 +639,16 @@ public class MediaBindingBean { if (videos.size() > 0) { return videos.iterator().next(); } - } else if (!VIDEO_FILES.accept(mediaFile)) { + } else if ((infoObject instanceof Episode || infoObject instanceof Movie) && !VIDEO_FILES.accept(mediaFile)) { + // prefer equal match from current context if possible + if (getContext() != null) { + for (Entry it : getContext().entrySet()) { + if (infoObject.equals(it.getValue()) && VIDEO_FILES.accept(it.getKey())) { + return it.getKey(); + } + } + } + // file is a subtitle, or nfo, etc String baseName = stripReleaseInfo(FileUtilities.getName(mediaFile)).toLowerCase(); File[] videos = mediaFile.getParentFile().listFiles(VIDEO_FILES); diff --git a/source/net/sourceforge/filebot/ui/rename/BindingDialog.java b/source/net/sourceforge/filebot/ui/rename/BindingDialog.java index 65d87352..d7d24150 100644 --- a/source/net/sourceforge/filebot/ui/rename/BindingDialog.java +++ b/source/net/sourceforge/filebot/ui/rename/BindingDialog.java @@ -119,7 +119,7 @@ class BindingDialog extends JDialog { if (bindingModel.executor.isShutdown()) return; - bindingModel.setModel(getSampleExpressions(), new MediaBindingBean(getInfoObject(), getMediaFile())); + bindingModel.setModel(getSampleExpressions(), new MediaBindingBean(getInfoObject(), getMediaFile(), null)); } }; diff --git a/source/net/sourceforge/filebot/ui/rename/ExpressionFormatter.java b/source/net/sourceforge/filebot/ui/rename/ExpressionFormatter.java index 9b4f08f7..a6393790 100644 --- a/source/net/sourceforge/filebot/ui/rename/ExpressionFormatter.java +++ b/source/net/sourceforge/filebot/ui/rename/ExpressionFormatter.java @@ -4,6 +4,7 @@ package net.sourceforge.filebot.ui.rename; import java.io.File; import java.text.Format; +import java.util.Map; import javax.script.ScriptException; @@ -20,7 +21,7 @@ class ExpressionFormatter implements MatchFormatter { private Format preview; private Class target; - + public ExpressionFormatter(String expression, Format preview, Class target) { if (expression == null || expression.isEmpty()) throw new IllegalArgumentException("Expression must not be null or empty"); @@ -31,29 +32,29 @@ class ExpressionFormatter implements MatchFormatter { } - + @Override public boolean canFormat(Match match) { // target object is required, file is optional return target.isInstance(match.getValue()) && (match.getCandidate() == null || match.getCandidate() instanceof File); } - + @Override public String preview(Match match) { return preview != null ? preview.format(match.getValue()) : match.getValue().toString(); } - + @Override - public synchronized String format(Match match) throws ScriptException { + public synchronized String format(Match match, Map context) throws ScriptException { // lazy initialize script engine if (format == null) { format = new ExpressionFormat(expression); } // evaluate the expression using the given bindings - Object bindingBean = new MediaBindingBean(match.getValue(), (File) match.getCandidate()); + Object bindingBean = new MediaBindingBean(match.getValue(), (File) match.getCandidate(), (Map) context); String result = format.format(bindingBean).trim(); // if result is empty, check for script exceptions diff --git a/source/net/sourceforge/filebot/ui/rename/FileNameFormatter.java b/source/net/sourceforge/filebot/ui/rename/FileNameFormatter.java index dd4ca204..0b541774 100644 --- a/source/net/sourceforge/filebot/ui/rename/FileNameFormatter.java +++ b/source/net/sourceforge/filebot/ui/rename/FileNameFormatter.java @@ -3,6 +3,7 @@ package net.sourceforge.filebot.ui.rename; import java.io.File; +import java.util.Map; import net.sourceforge.filebot.similarity.Match; import net.sourceforge.filebot.vfs.FileInfo; @@ -27,12 +28,12 @@ class FileNameFormatter implements MatchFormatter { @Override public String preview(Match match) { - return format(match); + return format(match, null); } @Override - public String format(Match match) { + public String format(Match match, Map context) { Object value = match.getValue(); if (value instanceof File) { diff --git a/source/net/sourceforge/filebot/ui/rename/FormatDialog.java b/source/net/sourceforge/filebot/ui/rename/FormatDialog.java index 8a8448d6..8e6e4b2c 100644 --- a/source/net/sourceforge/filebot/ui/rename/FormatDialog.java +++ b/source/net/sourceforge/filebot/ui/rename/FormatDialog.java @@ -405,7 +405,7 @@ class FormatDialog extends JDialog { media = new File(path); } - return new MediaBindingBean(info, media); + return new MediaBindingBean(info, media, null); } @@ -610,7 +610,7 @@ class FormatDialog extends JDialog { File file = dialog.getMediaFile(); // change sample - sample = new MediaBindingBean(info, file); + sample = new MediaBindingBean(info, file, null); // remember mode.persistentSample().setValue(info == null ? "" : mode.getFormat().format(info)); diff --git a/source/net/sourceforge/filebot/ui/rename/MatchFormatter.java b/source/net/sourceforge/filebot/ui/rename/MatchFormatter.java index 998fb961..1b612d80 100644 --- a/source/net/sourceforge/filebot/ui/rename/MatchFormatter.java +++ b/source/net/sourceforge/filebot/ui/rename/MatchFormatter.java @@ -2,6 +2,8 @@ package net.sourceforge.filebot.ui.rename; +import java.util.Map; + import net.sourceforge.filebot.similarity.Match; @@ -9,10 +11,10 @@ public interface MatchFormatter { public boolean canFormat(Match match); - + public String preview(Match match); - - public String format(Match match) throws Exception; + + public String format(Match match, Map context) throws Exception; } diff --git a/source/net/sourceforge/filebot/ui/rename/MovieFormatter.java b/source/net/sourceforge/filebot/ui/rename/MovieFormatter.java index 97da3414..3e35b1f8 100644 --- a/source/net/sourceforge/filebot/ui/rename/MovieFormatter.java +++ b/source/net/sourceforge/filebot/ui/rename/MovieFormatter.java @@ -5,6 +5,7 @@ package net.sourceforge.filebot.ui.rename; import static net.sourceforge.tuned.FileUtilities.*; import java.util.Formatter; +import java.util.Map; import net.sourceforge.filebot.similarity.Match; import net.sourceforge.filebot.web.MoviePart; @@ -20,12 +21,12 @@ class MovieFormatter implements MatchFormatter { @Override public String preview(Match match) { - return format(match); + return format(match, null); } @Override - public String format(Match match) { + public String format(Match match, Map context) { MoviePart video = (MoviePart) match.getValue(); Formatter name = new Formatter(new StringBuilder()); diff --git a/source/net/sourceforge/filebot/ui/rename/RenameModel.java b/source/net/sourceforge/filebot/ui/rename/RenameModel.java index bba8f1a5..7957d8e9 100644 --- a/source/net/sourceforge/filebot/ui/rename/RenameModel.java +++ b/source/net/sourceforge/filebot/ui/rename/RenameModel.java @@ -7,10 +7,13 @@ import static net.sourceforge.tuned.FileUtilities.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; @@ -45,12 +48,12 @@ public class RenameModel extends MatchModel { @Override public String preview(Match match) { - return format(match); + return format(match, null); } @Override - public String format(Match match) { + public String format(Match match, Map context) { // clean up path separators like / or \ return replacePathSeparators(String.valueOf(match.getValue())).trim(); } @@ -207,7 +210,7 @@ public class RenameModel extends MatchModel { Match match = getMatch(index); // create new future - final FormattedFuture future = new FormattedFuture(match, getFormatter(match)); + final FormattedFuture future = new FormattedFuture(match, getFormatter(match), getContext()); // update data if (type == ListEvent.INSERT) { @@ -255,7 +258,7 @@ public class RenameModel extends MatchModel { for (int i = 0; i < size(); i++) { FormattedFuture obsolete = futures.get(i); - FormattedFuture future = new FormattedFuture(obsolete.getMatch(), getFormatter(obsolete.getMatch())); + FormattedFuture future = new FormattedFuture(obsolete.getMatch(), getFormatter(obsolete.getMatch()), getContext()); // replace and cancel old future cancel(futures.set(i, future)); @@ -270,6 +273,23 @@ public class RenameModel extends MatchModel { } + private Map getContext() { + return new AbstractMap() { + + @Override + public Set> entrySet() { + Set> context = new LinkedHashSet>(); + for (Match it : matches()) { + if (it.getValue() != null && it.getCandidate() != null) { + context.add(new SimpleImmutableEntry(it.getCandidate(), it.getValue())); + } + } + return context; + } + }; + } + + private void submit(FormattedFuture future) { // observe and enqueue worker task future.addPropertyChangeListener(futureListener); @@ -305,13 +325,15 @@ public class RenameModel extends MatchModel { public static class FormattedFuture extends SwingWorker { private final Match match; + private final Map context; private final MatchFormatter formatter; - private FormattedFuture(Match match, MatchFormatter formatter) { + private FormattedFuture(Match match, MatchFormatter formatter, Map context) { this.match = match; this.formatter = formatter; + this.context = context; } @@ -332,7 +354,7 @@ public class RenameModel extends MatchModel { @Override protected String doInBackground() throws Exception { - return formatter.format(match).trim(); + return formatter.format(match, context).trim(); }