1
0
mirror of https://github.com/mitb-archive/filebot synced 2025-03-09 13:59:49 -04:00

++ Command Line Interface ++ ヾ(@⌒ー⌒@)ノ

This commit is contained in:
Reinhard Pointner 2011-09-09 14:50:01 +00:00
parent faf6095e3d
commit af60f6b6f1
32 changed files with 695 additions and 245 deletions

View File

@ -1,83 +0,0 @@
package net.sourceforge.filebot;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import net.sourceforge.filebot.ui.transfer.FileTransferable;
import net.sourceforge.tuned.FileUtilities;
public class ArgumentBean {
@Option(name = "-help", usage = "Print this help message")
private boolean help = false;
@Option(name = "-clear", usage = "Clear application settings")
private boolean clear = false;
@Option(name = "-open", usage = "Open file", metaVar = "<file>")
private boolean open = false;
@Option(name = "--sfv", usage = "Open SFV panel", metaVar = "<file>")
private boolean sfv = false;
@Argument
private List<File> arguments = new LinkedList<File>();
public boolean help() {
return help;
}
public boolean clear() {
return clear;
}
public boolean open() {
return open;
}
public boolean sfv() {
return sfv || (open && FileUtilities.containsOnly(arguments, MediaTypes.getDefaultFilter("verification")));
}
public List<File> arguments() {
return arguments;
}
public FileTransferable transferable() {
List<File> files = new ArrayList<File>(arguments.size());
for (File argument : arguments) {
if (argument.exists()) {
try {
// path may be relative, use absolute path
files.add(argument.getCanonicalFile());
} catch (IOException e) {
Logger.getLogger(getClass().getName()).log(Level.SEVERE, e.toString(), e);
}
} else {
// file doesn't exist
Logger.getLogger(getClass().getName()).log(Level.WARNING, String.format("Invalid File: %s", argument));
}
}
return new FileTransferable(files);
}
}

View File

@ -23,10 +23,13 @@ import javax.swing.UIManager;
import org.kohsuke.args4j.CmdLineParser;
import net.sf.ehcache.CacheManager;
import net.sourceforge.filebot.cli.ArgumentBean;
import net.sourceforge.filebot.cli.ArgumentProcessor;
import net.sourceforge.filebot.format.ExpressionFormat;
import net.sourceforge.filebot.ui.MainFrame;
import net.sourceforge.filebot.ui.SinglePanelFrame;
import net.sourceforge.filebot.ui.panel.sfv.SfvPanelBuilder;
import net.sourceforge.filebot.ui.transfer.FileTransferable;
public class Main {
@ -42,57 +45,69 @@ public class Main {
// parse arguments
final ArgumentBean argumentBean = initializeArgumentBean(args);
if (argumentBean.help()) {
printUsage(argumentBean);
if (argumentBean.printHelp()) {
new CmdLineParser(argumentBean).printUsage(System.out);
// just print help message and exit afterwards
System.exit(0);
}
if (argumentBean.clear()) {
if (argumentBean.clearUserData()) {
// clear preferences and cache
Settings.forPackage(Main.class).clear();
CacheManager.getInstance().clearAll();
}
try {
// use native laf an all platforms
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
Logger.getLogger(Main.class.getName()).log(Level.WARNING, e.getMessage(), e);
if (argumentBean.runCLI()) {
// run cmdline interface and then exit
System.exit(new ArgumentProcessor().process(argumentBean));
}
// Start user interface
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame;
if (argumentBean.sfv()) {
// single panel frame
frame = new SinglePanelFrame(new SfvPanelBuilder()).publish(argumentBean.transferable());
} else {
// default frame
frame = new MainFrame();
}
frame.setLocationByPlatform(true);
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
try {
// restore previous size and location
restoreWindowBounds(frame, Settings.forPackage(MainFrame.class));
// use native laf an all platforms
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
// don't care, doesn't make a difference
Logger.getLogger(Main.class.getName()).log(Level.WARNING, e.getMessage(), e);
}
// start application
frame.setVisible(true);
startUserInterface(argumentBean);
}
});
}
private static void startUserInterface(ArgumentBean args) {
JFrame frame;
if (args.openSFV()) {
// single panel frame
FileTransferable files = new FileTransferable(args.getFiles(false));
frame = new SinglePanelFrame(new SfvPanelBuilder()).publish(files);
} else {
// default frame
frame = new MainFrame();
}
frame.setLocationByPlatform(true);
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
try {
// restore previous size and location
restoreWindowBounds(frame, Settings.forPackage(MainFrame.class));
} catch (Exception e) {
// don't care, doesn't make a difference
}
// start application
frame.setVisible(true);
}
private static void restoreWindowBounds(final JFrame window, final Settings settings) {
// store bounds on close
window.addWindowListener(new WindowAdapter() {
@ -184,15 +199,4 @@ public class Main {
return argumentBean;
}
/**
* Print command line argument usage.
*/
private static void printUsage(ArgumentBean argumentBean) {
System.out.println("Options:");
CmdLineParser parser = new CmdLineParser(argumentBean);
parser.printUsage(System.out);
}
}

View File

@ -0,0 +1,131 @@
package net.sourceforge.filebot.cli;
import static java.util.Collections.*;
import static net.sourceforge.tuned.FileUtilities.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.script.ScriptException;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
import net.sourceforge.filebot.MediaTypes;
import net.sourceforge.filebot.WebServices;
import net.sourceforge.filebot.format.ExpressionFormat;
import net.sourceforge.filebot.ui.Language;
import net.sourceforge.filebot.web.EpisodeListProvider;
import net.sourceforge.filebot.web.MovieIdentificationService;
public class ArgumentBean {
@Option(name = "-rename-series", usage = "Rename episodes", metaVar = "folder")
public boolean renameSeries;
@Option(name = "-rename-movie", usage = "Rename movie", metaVar = "folder")
public boolean renameMovie;
@Option(name = "-get-subtitles", usage = "Fetch subtitles", metaVar = "folder")
public boolean getSubtitles;
@Option(name = "--format", usage = "Episode naming scheme", metaVar = "expression")
public String format = "{n} - {s+'x'}{e.pad(2)} - {t}";
@Option(name = "--q", usage = "Search query", metaVar = "name")
public String query = null;
@Option(name = "--db", usage = "Episode database")
public String db = null;
@Option(name = "--lang", usage = "Language", metaVar = "language code")
public String lang = "en";
@Option(name = "-help", usage = "Print this help message")
public boolean help = false;
@Option(name = "-open", usage = "Open file", metaVar = "<file>")
public boolean open = false;
@Option(name = "-clear", usage = "Clear application settings")
public boolean clear = false;
@Argument
public List<String> arguments = new ArrayList<String>();
public boolean runCLI() {
return getSubtitles || renameSeries || renameMovie;
}
public boolean printHelp() {
return help;
}
public boolean openSFV() {
return open && containsOnly(getFiles(false), MediaTypes.getDefaultFilter("verification"));
}
public boolean clearUserData() {
return clear;
}
public List<File> getFiles(boolean resolveFolders) {
List<File> files = new ArrayList<File>();
// resolve given paths
for (String argument : arguments) {
try {
File file = new File(argument).getCanonicalFile();
// resolve folders
files.addAll(resolveFolders && file.isDirectory() ? listFiles(singleton(file), 0) : singleton(file));
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
return files;
}
public ExpressionFormat getEpisodeFormat() throws ScriptException {
return new ExpressionFormat(format);
}
public Language getLanguage() {
Language language = Language.getLanguage(lang);
if (language == null)
throw new IllegalArgumentException("Illegal language code: " + lang);
return language;
}
public EpisodeListProvider getEpisodeListProvider() throws Exception {
if (db == null)
return WebServices.TVRage;
return (EpisodeListProvider) WebServices.class.getField(db).get(null);
}
public MovieIdentificationService getMovieIdentificationService() throws Exception {
if (db == null)
return WebServices.OpenSubtitles;
return (MovieIdentificationService) WebServices.class.getField(db).get(null);
}
}

View File

@ -0,0 +1,371 @@
package net.sourceforge.filebot.cli;
import static java.lang.String.*;
import static net.sourceforge.filebot.MediaTypes.*;
import static net.sourceforge.filebot.cli.CLILogging.*;
import static net.sourceforge.tuned.FileUtilities.*;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.AbstractMap.SimpleImmutableEntry;
import java.util.Map.Entry;
import net.sourceforge.filebot.WebServices;
import net.sourceforge.filebot.format.EpisodeBindingBean;
import net.sourceforge.filebot.format.ExpressionFormat;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.similarity.Matcher;
import net.sourceforge.filebot.similarity.NameSimilarityMetric;
import net.sourceforge.filebot.similarity.SeriesNameMatcher;
import net.sourceforge.filebot.similarity.SimilarityMetric;
import net.sourceforge.filebot.ui.Language;
import net.sourceforge.filebot.ui.panel.rename.HistorySpooler;
import net.sourceforge.filebot.ui.panel.rename.MatchSimilarityMetric;
import net.sourceforge.filebot.vfs.ArchiveType;
import net.sourceforge.filebot.vfs.MemoryFile;
import net.sourceforge.filebot.web.Episode;
import net.sourceforge.filebot.web.EpisodeListProvider;
import net.sourceforge.filebot.web.MovieDescriptor;
import net.sourceforge.filebot.web.MovieIdentificationService;
import net.sourceforge.filebot.web.SearchResult;
import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.filebot.web.SubtitleProvider;
import net.sourceforge.filebot.web.VideoHashSubtitleService;
public class ArgumentProcessor {
public int process(ArgumentBean args) throws Exception {
try {
SortedSet<File> files = new TreeSet<File>(args.getFiles(true));
if (args.getSubtitles) {
List<File> subtitles = getSubtitles(files, args.query, args.getLanguage());
files.addAll(subtitles);
}
if (args.renameSeries) {
renameSeries(files, args.query, args.getEpisodeFormat(), args.getEpisodeListProvider(), args.getLanguage().toLocale());
}
if (args.renameMovie) {
renameMovie(files, args.getMovieIdentificationService(), args.getLanguage().toLocale());
}
CLILogger.fine("Done ヾ(@⌒ー⌒@)");
return 0;
} catch (Exception e) {
CLILogger.severe(e.getMessage());
CLILogger.fine("Failure (°_°)");
return -1;
}
}
public void renameSeries(Collection<File> files, String query, ExpressionFormat format, EpisodeListProvider datasource, Locale locale) throws Exception {
List<File> mediaFiles = filter(files, VIDEO_FILES, SUBTITLE_FILES);
if (mediaFiles.isEmpty()) {
throw new IllegalArgumentException("No video or subtitle files: " + files);
}
// auto-detect series name if not given
if (query == null) {
Collection<String> possibleNames = new SeriesNameMatcher().matchAll(mediaFiles.toArray(new File[0]));
if (possibleNames.size() == 1) {
query = possibleNames.iterator().next();
CLILogger.config("Auto-detected series name: " + possibleNames);
} else {
throw new Exception("Failed to auto-detect series name: " + possibleNames);
}
}
CLILogger.fine(format("Fetching episode data for [%s] from [%s]", query, datasource.getName()));
// find series on the web
SearchResult hit = selectSearchResult(query, datasource.search(query, locale));
// fetch episode list
List<Episode> episodes = datasource.getEpisodeList(hit, locale);
List<Match<File, Episode>> matches = new ArrayList<Match<File, Episode>>();
matches.addAll(match(filter(mediaFiles, VIDEO_FILES), episodes));
matches.addAll(match(filter(mediaFiles, SUBTITLE_FILES), episodes));
if (matches.isEmpty()) {
throw new RuntimeException("Unable to match files to episode data");
}
// map old files to new paths by applying formatting and validating filenames
Map<File, String> renameMap = new LinkedHashMap<File, String>();
for (Match<File, Episode> match : matches) {
File file = match.getValue();
String newName = format.format(new EpisodeBindingBean(match.getCandidate(), file));
if (isInvalidFileName(newName)) {
CLILogger.config("Stripping invalid characters from new name: " + newName);
newName = validateFileName(newName);
}
renameMap.put(file, newName + "." + getExtension(file));
}
// rename episodes
renameAll(renameMap);
}
public void renameMovie(Collection<File> files, MovieIdentificationService datasource, Locale locale) throws Exception {
File[] movieFiles = filter(files, VIDEO_FILES).toArray(new File[0]);
if (movieFiles.length <= 0) {
throw new IllegalArgumentException("No video files: " + files);
}
CLILogger.fine(format("Looking up movie by filehash via [%s]", datasource.getName()));
// match movie hashes online
MovieDescriptor[] movieByFileHash = datasource.getMovieDescriptors(movieFiles, locale);
// map old files to new paths by applying formatting and validating filenames
Map<File, String> renameMap = new LinkedHashMap<File, String>();
for (int i = 0; i < movieFiles.length; i++) {
if (movieByFileHash[i] != null) {
String newName = movieByFileHash[i].toString();
if (isInvalidFileName(newName)) {
CLILogger.config("Stripping invalid characters from new path: " + newName);
newName = validateFileName(newName);
}
renameMap.put(movieFiles[i], newName + "." + getExtension(movieFiles[i]));
} else {
CLILogger.warning("No matching movie: " + movieFiles[i]);
}
}
// handle subtitle files
for (File subtitleFile : filter(files, SUBTITLE_FILES)) {
// check if subtitle corresponds to a movie file (same name, different extension)
for (int i = 0; i < movieByFileHash.length; i++) {
if (movieByFileHash != null) {
String subtitleName = getName(subtitleFile);
String movieName = getName(movieFiles[i]);
if (subtitleName.equalsIgnoreCase(movieName)) {
String movieDestinationName = renameMap.get(movieFiles[i]);
renameMap.put(subtitleFile, getNameWithoutExtension(movieDestinationName) + "." + getExtension(subtitleFile));
// movie match found, we're done
break;
}
}
}
}
// rename episodes
renameAll(renameMap);
}
public List<File> getSubtitles(Collection<File> files, String query, Language language) throws Exception {
// match movie hashes online
Set<File> videos = new TreeSet<File>(filter(files, VIDEO_FILES));
List<File> downloadedSubtitles = new ArrayList<File>();
if (videos.isEmpty()) {
throw new IllegalArgumentException("No video files: " + files);
}
// lookup subtitles by hash
for (VideoHashSubtitleService service : WebServices.getVideoHashSubtitleServices()) {
if (videos.isEmpty())
break;
CLILogger.fine("Looking up subtitles by filehash via " + service.getName());
for (Entry<File, List<SubtitleDescriptor>> it : service.getSubtitleList(videos.toArray(new File[0]), language.getName()).entrySet()) {
if (it.getValue() != null && it.getValue().size() > 0) {
// auto-select first element if there are multiple hash matches for the same video files
File subtitle = fetchSubtitle(it.getValue().get(0), it.getKey());
// download complete, cross this video off the list
videos.remove(it.getKey());
downloadedSubtitles.add(subtitle);
}
}
}
// lookup subtitles by query and filename
if (query != null && videos.size() > 0) {
for (SubtitleProvider service : WebServices.getSubtitleProviders()) {
try {
CLILogger.fine(format("Searching for [%s] at [%s]", query, service.getName()));
SearchResult searchResult = selectSearchResult(query, service.search(query));
CLILogger.config("Retrieving subtitles for " + searchResult.getName());
List<SubtitleDescriptor> subtitles = service.getSubtitleList(searchResult, language.getName());
for (File video : videos.toArray(new File[0])) {
String filename = getName(video); // get name without extension
for (SubtitleDescriptor descriptor : subtitles) {
if (filename.equalsIgnoreCase(descriptor.getName())) {
File subtitle = fetchSubtitle(descriptor, video);
// download complete, cross this video off the list
videos.remove(video);
downloadedSubtitles.add(subtitle);
}
}
}
} catch (Exception e) {
CLILogger.warning(e.getMessage());
}
}
}
// no subtitles for remaining video files
for (File video : videos) {
CLILogger.warning("No matching subtitles found: " + video);
}
return downloadedSubtitles;
}
private File fetchSubtitle(SubtitleDescriptor descriptor, File movieFile) throws Exception {
// fetch subtitle archive
CLILogger.info(format("Fetching [%s.%s]", descriptor.getName(), descriptor.getType()));
ByteBuffer downloadedData = descriptor.fetch();
// extract subtitles from archive
ArchiveType type = ArchiveType.forName(descriptor.getType());
MemoryFile subtitleData;
if (type != ArchiveType.UNDEFINED) {
// extract subtitle from archive
subtitleData = type.fromData(downloadedData).iterator().next();
} else {
// assume that the fetched data is the subtitle
subtitleData = new MemoryFile(descriptor.getName() + "." + descriptor.getType(), downloadedData);
}
// subtitle filename is based on movie filename
String subtitleFileName = getNameWithoutExtension(movieFile.getName()) + "." + getExtension(subtitleData.getName());
File destination = new File(movieFile.getParentFile(), validateFileName(subtitleFileName));
CLILogger.config(format("Writing [%s] to [%s]", subtitleData.getName(), destination.getName()));
writeFile(subtitleData.getData(), destination);
return destination;
}
private void renameAll(Map<File, String> renameMap) {
// rename files
List<Entry<File, File>> renameLog = new ArrayList<Entry<File, File>>();
try {
for (Entry<File, String> it : renameMap.entrySet()) {
try {
// rename file, throw exception on failure
File destination = rename(it.getKey(), it.getValue());
CLILogger.info(format("Renamed [%s] to [%s]", it.getKey(), it.getValue()));
// remember successfully renamed matches for history entry and possible revert
renameLog.add(new SimpleImmutableEntry<File, File>(it.getKey(), destination));
} catch (IOException e) {
CLILogger.warning(format("Failed to rename [%s]", it.getKey()));
throw e;
}
}
} catch (Exception e) {
// could not rename one of the files, revert all changes
CLILogger.severe(e.getMessage());
// revert rename operations in reverse order
for (ListIterator<Entry<File, File>> iterator = renameLog.listIterator(renameLog.size()); iterator.hasPrevious();) {
Entry<File, File> mapping = iterator.previous();
// revert rename
if (mapping.getValue().renameTo(mapping.getKey())) {
// remove reverted rename operation from log
CLILogger.info("Reverted filename: " + mapping.getKey());
} else {
// failed to revert rename operation
CLILogger.severe("Failed to revert filename: " + mapping.getValue());
}
}
}
if (renameLog.size() > 0) {
// update rename history
HistorySpooler.getInstance().append(renameMap.entrySet());
// printer number of renamed files if any
CLILogger.fine(format("Renamed %d files", renameLog.size()));
}
}
private List<Match<File, Episode>> match(List<File> files, List<Episode> episodes) throws Exception {
// strict SxE metric, don't allow in-between values
SimilarityMetric metric = new SimilarityMetric() {
@Override
public float getSimilarity(Object o1, Object o2) {
return MatchSimilarityMetric.EpisodeIdentifier.getSimilarity(o1, o2) >= 1 ? 1 : 0;
}
};
// fail-fast matcher
Matcher<File, Episode> matcher = new Matcher<File, Episode>(files, episodes, true, new SimilarityMetric[] { metric });
List<Match<File, Episode>> matches = matcher.match();
for (File failedMatch : matcher.remainingValues()) {
CLILogger.warning("No matching episode: " + failedMatch.getName());
}
return matches;
}
private SearchResult selectSearchResult(String query, Iterable<SearchResult> searchResults) throws IllegalArgumentException {
// auto-select most probable search result
List<SearchResult> probableMatches = new ArrayList<SearchResult>();
// use name similarity metric
SimilarityMetric metric = new NameSimilarityMetric();
// find probable matches using name similarity > 0.9
for (SearchResult result : searchResults) {
if (metric.getSimilarity(query, result.getName()) > 0.9) {
probableMatches.add(result);
}
}
if (probableMatches.size() != 1) {
throw new IllegalArgumentException("Failed to auto-select search result: " + probableMatches);
}
return probableMatches.get(0);
}
}

View File

@ -0,0 +1,56 @@
package net.sourceforge.filebot.cli;
import static java.lang.System.*;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
public class CLILogging extends Handler {
public static final Logger CLILogger = createCommandlineLogger("net.sourceforge.filebot.cli");
private static Logger createCommandlineLogger(String name) {
Logger log = Logger.getLogger(name);
log.setLevel(Level.ALL);
// don't use parent handlers
log.setUseParentHandlers(false);
// CLI handler
log.addHandler(new CLILogging());
return log;
}
@Override
public void publish(LogRecord record) {
// print messages to stdout
out.println(record.getMessage());
if (record.getThrown() != null) {
record.getThrown().printStackTrace(out);
}
// flush every message immediately
out.flush();
}
@Override
public void close() throws SecurityException {
}
@Override
public void flush() {
out.flush();
}
}

View File

@ -41,6 +41,11 @@ public class Language {
}
public Locale toLocale() {
return new Locale(getCode());
}
@Override
public Language clone() {
return new Language(code, name);
@ -102,4 +107,5 @@ public class Language {
return getLanguages(codes.toArray(new String[0]));
}
}

View File

@ -244,7 +244,7 @@ class EpisodeBindingDialog extends JDialog {
public void setEpisode(Episode episode) {
episodeTextField.setText(episode == null ? "" : EpisodeFormat.getDefaultInstance().format(episode));
episodeTextField.setText(episode == null ? "" : EpisodeFormat.SeasonEpisode.format(episode));
}
@ -255,7 +255,7 @@ class EpisodeBindingDialog extends JDialog {
public Episode getEpisode() {
try {
return EpisodeFormat.getDefaultInstance().parseObject(episodeTextField.getText());
return EpisodeFormat.Default.parseObject(episodeTextField.getText());
} catch (Exception e) {
return null;
}

View File

@ -41,7 +41,7 @@ class EpisodeExpressionFormatter implements MatchFormatter {
@Override
public String preview(Match<?, ?> match) {
return EpisodeFormat.getSeasonEpisodeInstance().format(match.getValue());
return EpisodeFormat.SeasonEpisode.format(match.getValue());
}

View File

@ -289,7 +289,7 @@ class EpisodeFormatDialog extends JDialog {
// restore episode
try {
episode = EpisodeFormat.getDefaultInstance().parseObject(persistentSampleEpisode.getValue());
episode = EpisodeFormat.Default.parseObject(persistentSampleEpisode.getValue());
} catch (Exception e) {
// default sample
episode = new Episode("Dark Angel", 3, 1, "Labyrinth", 42, null, new Date(2009, 6, 1));
@ -465,7 +465,7 @@ class EpisodeFormatDialog extends JDialog {
sample = new EpisodeBindingBean(episode, file);
// remember
persistentSampleEpisode.setValue(episode == null ? "" : EpisodeFormat.getDefaultInstance().format(sample.getEpisode()));
persistentSampleEpisode.setValue(episode == null ? "" : EpisodeFormat.Default.format(sample.getEpisode()));
persistentSampleFile.setValue(file == null ? "" : sample.getMediaFile().getAbsolutePath());
// reevaluate everything

View File

@ -5,6 +5,7 @@ package net.sourceforge.filebot.ui.panel.rename;
import java.io.File;
import net.sourceforge.filebot.similarity.Match;
import net.sourceforge.filebot.vfs.AbstractFile;
import net.sourceforge.tuned.FileUtilities;

View File

@ -58,6 +58,9 @@ class History {
public List<Element> elements() {
if (elements == null)
return emptyList();
return unmodifiableList(elements);
}

View File

@ -16,7 +16,7 @@ import java.util.logging.Logger;
import net.sourceforge.filebot.ui.panel.rename.History.Element;
final class HistorySpooler {
public final class HistorySpooler {
private static final HistorySpooler instance = new HistorySpooler();
@ -58,7 +58,9 @@ final class HistorySpooler {
}
// append to session history
sessionHistory.add(sequence);
if (sequence.size() > 0) {
sessionHistory.add(sequence);
}
}

View File

@ -16,6 +16,7 @@ import net.sourceforge.filebot.similarity.NumericSimilarityMetric;
import net.sourceforge.filebot.similarity.SeasonEpisodeMetric;
import net.sourceforge.filebot.similarity.SimilarityMetric;
import net.sourceforge.filebot.similarity.SeasonEpisodeMatcher.SxE;
import net.sourceforge.filebot.vfs.AbstractFile;
import net.sourceforge.filebot.web.Date;
import net.sourceforge.filebot.web.Episode;
import net.sourceforge.tuned.FileUtilities;

View File

@ -22,6 +22,7 @@ import net.sourceforge.filebot.hash.VerificationFileReader;
import net.sourceforge.filebot.torrent.Torrent;
import net.sourceforge.filebot.ui.transfer.ArrayTransferable;
import net.sourceforge.filebot.ui.transfer.FileTransferablePolicy;
import net.sourceforge.filebot.vfs.AbstractFile;
import net.sourceforge.filebot.web.Episode;
import net.sourceforge.tuned.FastFile;

View File

@ -4,12 +4,12 @@ package net.sourceforge.filebot.ui.panel.rename;
import static java.util.Collections.*;
import static net.sourceforge.filebot.ui.NotificationLogging.*;
import static net.sourceforge.tuned.FileUtilities.*;
import static net.sourceforge.tuned.ui.TunedUtilities.*;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
@ -90,31 +90,6 @@ class RenameAction extends AbstractAction {
}
private File rename(File file, String path) throws IOException {
File destination = new File(path);
// resolve destination
if (!destination.isAbsolute()) {
// same folder, different name
destination = new File(file.getParentFile(), path);
}
// make sure we that we can create the destination folder structure
File destinationFolder = destination.getParentFile();
// create parent folder if necessary
if (!destinationFolder.isDirectory() && !destinationFolder.mkdirs()) {
throw new IOException("Failed to create folder: " + destinationFolder);
}
if (!file.renameTo(destination)) {
throw new IOException("Failed to rename file: " + file.getName());
}
return destination;
}
private Iterable<Entry<File, String>> validate(Map<File, String> renameMap, Window parent) {
final List<Entry<File, String>> source = new ArrayList<Entry<File, String>>(renameMap.entrySet());

View File

@ -17,6 +17,7 @@ import javax.swing.TransferHandler;
import net.sourceforge.filebot.ui.transfer.ByteBufferTransferable;
import net.sourceforge.filebot.ui.transfer.ClipboardHandler;
import net.sourceforge.filebot.ui.transfer.TransferableExportHandler;
import net.sourceforge.filebot.vfs.MemoryFile;
class MemoryFileListExportHandler implements TransferableExportHandler, ClipboardHandler {

View File

@ -55,6 +55,7 @@ import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.ui.panel.subtitle.SubtitlePackage.Download.Phase;
import net.sourceforge.filebot.ui.transfer.DefaultTransferHandler;
import net.sourceforge.filebot.vfs.MemoryFile;
import net.sourceforge.tuned.ExceptionUtilities;
import net.sourceforge.tuned.ui.ListView;
import net.sourceforge.tuned.ui.TunedUtilities;
@ -294,7 +295,7 @@ class SubtitleDownloadComponent extends JComponent {
fc.setSelectedFile(new File(validateFileName(file.getName())));
if (fc.showSaveDialog(getWindow(this)) == JFileChooser.APPROVE_OPTION) {
write(file.getData(), fc.getSelectedFile());
writeFile(file.getData(), fc.getSelectedFile());
}
} else {
// multiple files
@ -307,7 +308,7 @@ class SubtitleDownloadComponent extends JComponent {
for (Object object : selection) {
MemoryFile file = (MemoryFile) object;
File destination = new File(folder, validateFileName(file.getName()));
write(file.getData(), destination);
writeFile(file.getData(), destination);
}
}
}

View File

@ -21,6 +21,8 @@ import javax.swing.SwingWorker;
import javax.swing.event.SwingPropertyChangeSupport;
import net.sourceforge.filebot.ui.Language;
import net.sourceforge.filebot.vfs.ArchiveType;
import net.sourceforge.filebot.vfs.MemoryFile;
import net.sourceforge.filebot.web.SubtitleDescriptor;
import net.sourceforge.tuned.FileUtilities;

View File

@ -14,6 +14,7 @@ import javax.swing.border.CompoundBorder;
import net.miginfocom.swing.MigLayout;
import net.sourceforge.filebot.ResourceManager;
import net.sourceforge.filebot.vfs.ArchiveType;
import net.sourceforge.tuned.ui.AbstractFancyListCellRenderer;

View File

@ -3,14 +3,12 @@ package net.sourceforge.filebot.ui.panel.subtitle;
import static java.lang.Math.*;
import static net.sourceforge.tuned.FileUtilities.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedList;
@ -22,6 +20,7 @@ import net.sourceforge.filebot.subtitle.SubRipWriter;
import net.sourceforge.filebot.subtitle.SubtitleElement;
import net.sourceforge.filebot.subtitle.SubtitleFormat;
import net.sourceforge.filebot.subtitle.SubtitleReader;
import net.sourceforge.filebot.vfs.MemoryFile;
import net.sourceforge.tuned.ByteBufferInputStream;
@ -91,21 +90,7 @@ final class SubtitleUtilities {
}
// write to file
write(encoding.encode(CharBuffer.wrap(buffer)), destination);
}
/**
* Write {@link ByteBuffer} to {@link File}.
*/
public static void write(ByteBuffer data, File destination) throws IOException {
FileChannel fileChannel = new FileOutputStream(destination).getChannel();
try {
fileChannel.write(data);
} finally {
fileChannel.close();
}
writeFile(encoding.encode(CharBuffer.wrap(buffer)), destination);
}

View File

@ -4,7 +4,7 @@ package net.sourceforge.filebot.ui.panel.subtitle;
import static javax.swing.BorderFactory.*;
import static javax.swing.JOptionPane.*;
import static net.sourceforge.filebot.ui.panel.subtitle.SubtitleUtilities.*;
import static net.sourceforge.tuned.FileUtilities.*;
import java.awt.Color;
import java.awt.Component;
@ -718,7 +718,7 @@ class VideoHashSubtitleDownloadDialog extends JDialog {
return null;
// save to file
write(data, destination);
writeFile(data, destination);
return destination;
} catch (Exception e) {

View File

@ -1,13 +1,13 @@
package net.sourceforge.filebot.ui.panel.rename;
package net.sourceforge.filebot.vfs;
class AbstractFile {
public class AbstractFile {
private final String name;
private final long length;
public AbstractFile(String name, long length) {
this.name = name;
this.length = length;

View File

@ -1,12 +1,13 @@
package net.sourceforge.filebot.ui.panel.subtitle;
package net.sourceforge.filebot.vfs;
import java.nio.ByteBuffer;
import java.util.Collections;
enum ArchiveType {
public enum ArchiveType {
ZIP {
@Override
@ -31,8 +32,7 @@ enum ArchiveType {
return Collections.emptySet();
}
};
public static ArchiveType forName(String name) {
if ("zip".equalsIgnoreCase(name))
return ZIP;

View File

@ -1,11 +1,11 @@
package net.sourceforge.filebot.ui.panel.subtitle;
package net.sourceforge.filebot.vfs;
import java.nio.ByteBuffer;
class MemoryFile {
public class MemoryFile {
private final String path;

View File

@ -1,5 +1,5 @@
package net.sourceforge.filebot.ui.panel.subtitle;
package net.sourceforge.filebot.vfs;
import java.io.IOException;
@ -17,7 +17,7 @@ import de.innosystec.unrar.exception.RarException;
import de.innosystec.unrar.rarfile.FileHeader;
class RarArchive implements Iterable<MemoryFile> {
public class RarArchive implements Iterable<MemoryFile> {
private final ByteBuffer data;

View File

@ -1,5 +1,5 @@
package net.sourceforge.filebot.ui.panel.subtitle;
package net.sourceforge.filebot.vfs;
import java.io.IOException;
@ -14,7 +14,7 @@ import net.sourceforge.tuned.ByteBufferInputStream;
import net.sourceforge.tuned.ByteBufferOutputStream;
class ZipArchive implements Iterable<MemoryFile> {
public class ZipArchive implements Iterable<MemoryFile> {
private final ByteBuffer data;

View File

@ -106,7 +106,7 @@ public class Episode implements Serializable {
@Override
public String toString() {
return EpisodeFormat.getSeasonEpisodeInstance().format(this);
return EpisodeFormat.SeasonEpisode.format(this);
}
}

View File

@ -12,18 +12,11 @@ import java.util.regex.Pattern;
public class EpisodeFormat extends Format {
private boolean includeAirdate = true;
private boolean includeSpecial = true;
public static final EpisodeFormat SeasonEpisode = new EpisodeFormat(true, false);
public static final EpisodeFormat Default = new EpisodeFormat(true, true);
public static EpisodeFormat getSeasonEpisodeInstance() {
return new EpisodeFormat(true, false);
}
public static EpisodeFormat getDefaultInstance() {
return new EpisodeFormat(true, true);
}
private final boolean includeAirdate;
private final boolean includeSpecial;
public EpisodeFormat(boolean includeSpecial, boolean includeAirdate) {

View File

@ -9,6 +9,9 @@ import java.util.Locale;
public interface MovieIdentificationService {
public String getName();
public List<MovieDescriptor> searchMovie(String query, Locale locale) throws Exception;

View File

@ -6,10 +6,13 @@ import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@ -26,7 +29,32 @@ import com.ibm.icu.text.CharsetMatch;
public final class FileUtilities {
public static byte[] readAll(File source) throws IOException {
public static File rename(File source, String newPath) throws IOException {
File destination = new File(newPath);
// resolve destination
if (!destination.isAbsolute()) {
// same folder, different name
destination = new File(source.getParentFile(), newPath);
}
// make sure we that we can create the destination folder structure
File destinationFolder = destination.getParentFile();
// create parent folder if necessary
if (!destinationFolder.isDirectory() && !destinationFolder.mkdirs()) {
throw new IOException("Failed to create folder: " + destinationFolder);
}
if (!source.renameTo(destination)) {
throw new IOException("Failed to rename file: " + source.getName());
}
return destination;
}
public static byte[] readFile(File source) throws IOException {
InputStream in = new FileInputStream(source);
try {
@ -46,6 +74,17 @@ public final class FileUtilities {
}
public static void writeFile(ByteBuffer data, File destination) throws IOException {
FileChannel fileChannel = new FileOutputStream(destination).getChannel();
try {
fileChannel.write(data);
} finally {
fileChannel.close();
}
}
public static Reader createTextReader(File file) throws IOException {
CharsetDetector detector = new CharsetDetector();
detector.setDeclaredEncoding("UTF-8"); // small boost for UTF-8 as default encoding

View File

@ -1,43 +0,0 @@
package net.sourceforge.filebot;
import static org.junit.Assert.*;
import org.junit.Test;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
public class ArgumentBeanTest {
@Test
public void clear() throws Exception {
ArgumentBean bean = parse("-clear", "--sfv", "One Piece", "Naruto");
assertTrue(bean.clear());
assertFalse(bean.help());
assertEquals("One Piece", bean.arguments().get(0).toString());
assertEquals("Naruto", bean.arguments().get(1).toString());
}
@Test
public void noClear() throws Exception {
ArgumentBean bean = parse("--sfv", "One Piece.sfv");
assertFalse(bean.help());
assertFalse(bean.clear());
assertEquals("One Piece.sfv", bean.arguments().get(0).toString());
}
private static ArgumentBean parse(String... args) throws CmdLineException {
ArgumentBean bean = new ArgumentBean();
new CmdLineParser(bean).parseArgument(args);
return bean;
}
}

View File

@ -16,7 +16,7 @@ import net.sourceforge.filebot.web.WebTestSuite;
@RunWith(Suite.class)
@SuiteClasses( { SimilarityTestSuite.class, WebTestSuite.class, ArgumentBeanTest.class, ExpressionFormatTest.class, VerificationFormatTest.class, MatchModelTest.class, MatchSimilarityMetricTest.class, SubtitleReaderTestSuite.class })
@SuiteClasses( { SimilarityTestSuite.class, WebTestSuite.class, ExpressionFormatTest.class, VerificationFormatTest.class, MatchModelTest.class, MatchSimilarityMetricTest.class, SubtitleReaderTestSuite.class })
public class FileBotTestSuite {
}