Refactor CmdlineInterface with proper types for all parameters

This commit is contained in:
Reinhard Pointner 2016-11-28 06:10:42 +08:00
parent b42149e927
commit 7a0a36b528
11 changed files with 257 additions and 229 deletions

View File

@ -15,6 +15,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Stream;
import net.filebot.media.XattrMetaInfoProvider; import net.filebot.media.XattrMetaInfoProvider;
import net.filebot.similarity.MetricAvg; import net.filebot.similarity.MetricAvg;
@ -40,7 +41,6 @@ import net.filebot.web.TVMazeClient;
import net.filebot.web.TheTVDBClient; import net.filebot.web.TheTVDBClient;
import net.filebot.web.TheTVDBClientV1; import net.filebot.web.TheTVDBClientV1;
import net.filebot.web.VideoHashSubtitleService; import net.filebot.web.VideoHashSubtitleService;
import one.util.streamex.StreamEx;
/** /**
* Reuse the same web service client so login, cache, etc. can be shared. * Reuse the same web service client so login, cache, etc. can be shared.
@ -72,29 +72,39 @@ public final class WebServices {
public static final XattrMetaInfoProvider XattrMetaData = new XattrMetaInfoProvider(); public static final XattrMetaInfoProvider XattrMetaData = new XattrMetaInfoProvider();
public static final ID3Lookup MediaInfoID3 = new ID3Lookup(); public static final ID3Lookup MediaInfoID3 = new ID3Lookup();
public static EpisodeListProvider[] getEpisodeListProviders() { public static Datasource[] getServices() {
return new EpisodeListProvider[] { TheTVDB, AniDB, TheMovieDB_TV, TVmaze }; return new Datasource[] { TheMovieDB, OMDb, TheTVDB, AniDB, TheMovieDB_TV, TVmaze, AcoustID, MediaInfoID3, XattrMetaData, OpenSubtitles, Shooter, TheTVDBv2, FanartTV };
} }
public static MovieIdentificationService[] getMovieIdentificationServices() { public static MovieIdentificationService[] getMovieIdentificationServices() {
return new MovieIdentificationService[] { TheMovieDB, OMDb }; return new MovieIdentificationService[] { TheMovieDB, OMDb };
} }
public static SubtitleProvider[] getSubtitleProviders() { public static EpisodeListProvider[] getEpisodeListProviders() {
return new SubtitleProvider[] { OpenSubtitles }; return new EpisodeListProvider[] { TheTVDB, AniDB, TheMovieDB_TV, TVmaze };
}
public static VideoHashSubtitleService[] getVideoHashSubtitleServices(Locale locale) {
if (locale.equals(Locale.CHINESE))
return new VideoHashSubtitleService[] { OpenSubtitles, Shooter };
else
return new VideoHashSubtitleService[] { OpenSubtitles };
} }
public static MusicIdentificationService[] getMusicIdentificationServices() { public static MusicIdentificationService[] getMusicIdentificationServices() {
return new MusicIdentificationService[] { AcoustID, MediaInfoID3 }; return new MusicIdentificationService[] { AcoustID, MediaInfoID3 };
} }
public static SubtitleProvider[] getSubtitleProviders(Locale locale) {
return new SubtitleProvider[] { OpenSubtitles };
}
public static VideoHashSubtitleService[] getVideoHashSubtitleServices(Locale locale) {
// special support for 射手网 for Chinese language subtitles
if (locale.equals(Locale.CHINESE)) {
return new VideoHashSubtitleService[] { OpenSubtitles, Shooter };
}
return new VideoHashSubtitleService[] { OpenSubtitles };
}
public static Datasource getService(String name) {
return getService(name, getServices());
}
public static EpisodeListProvider getEpisodeListProvider(String name) { public static EpisodeListProvider getEpisodeListProvider(String name) {
return getService(name, getEpisodeListProviders()); return getService(name, getEpisodeListProviders());
} }
@ -107,8 +117,10 @@ public final class WebServices {
return getService(name, getMusicIdentificationServices()); return getService(name, getMusicIdentificationServices());
} }
public static <T extends Datasource> T getService(String name, T[] services) { public static <T extends Datasource> T getService(String name, T... services) {
return StreamEx.of(services).findFirst(it -> it.getIdentifier().equalsIgnoreCase(name) || it.getName().equalsIgnoreCase(name)).orElse(null); return stream(services).filter(it -> {
return it.getIdentifier().equalsIgnoreCase(name) || it.getName().equalsIgnoreCase(name);
}).findFirst().orElse(null);
} }
public static final ExecutorService requestThreadPool = Executors.newCachedThreadPool(); public static final ExecutorService requestThreadPool = Executors.newCachedThreadPool();
@ -127,7 +139,8 @@ public final class WebServices {
private SearchResult merge(SearchResult prime, List<SearchResult> group) { private SearchResult merge(SearchResult prime, List<SearchResult> group) {
int id = prime.getId(); int id = prime.getId();
String name = prime.getName(); String name = prime.getName();
String[] aliasNames = StreamEx.of(group).flatMap(it -> stream(it.getAliasNames())).remove(name::equals).distinct().toArray(String[]::new);
String[] aliasNames = group.stream().flatMap(it -> stream(it.getAliasNames())).filter(n -> !n.equals(name)).distinct().toArray(String[]::new);
return new SearchResult(id, name, aliasNames); return new SearchResult(id, name, aliasNames);
} }
@ -138,7 +151,7 @@ public final class WebServices {
Future<List<SearchResult>> localSearch = requestThreadPool.submit(() -> localIndex.get().search(query)); Future<List<SearchResult>> localSearch = requestThreadPool.submit(() -> localIndex.get().search(query));
// combine alias names into a single search results, and keep API search name as primary name // combine alias names into a single search results, and keep API search name as primary name
Map<Integer, SearchResult> results = StreamEx.of(apiSearch.get()).append(localSearch.get()).groupingBy(SearchResult::getId, collectingAndThen(toList(), group -> merge(group.get(0), group))); Map<Integer, SearchResult> results = Stream.concat(apiSearch.get().stream(), localSearch.get().stream()).collect(groupingBy(SearchResult::getId, collectingAndThen(toList(), group -> merge(group.get(0), group))));
return sortBySimilarity(results.values(), singleton(query), getSeriesMatchMetric()); return sortBySimilarity(results.values(), singleton(query), getSeriesMatchMetric());
} }

View File

@ -2,15 +2,20 @@ package net.filebot.cli;
import static java.util.Collections.*; import static java.util.Collections.*;
import static net.filebot.Logging.*; import static net.filebot.Logging.*;
import static net.filebot.hash.VerificationUtilities.*;
import static net.filebot.subtitle.SubtitleUtilities.*;
import static net.filebot.util.FileUtilities.*; import static net.filebot.util.FileUtilities.*;
import java.io.File; import java.io.File;
import java.io.FileFilter;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.logging.Level; import java.util.logging.Level;
import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Argument;
@ -22,6 +27,14 @@ import org.kohsuke.args4j.spi.ExplicitBooleanOptionHandler;
import net.filebot.Language; import net.filebot.Language;
import net.filebot.StandardRenameAction; import net.filebot.StandardRenameAction;
import net.filebot.WebServices;
import net.filebot.format.ExpressionFileFilter;
import net.filebot.format.ExpressionFilter;
import net.filebot.format.ExpressionFormat;
import net.filebot.hash.HashType;
import net.filebot.subtitle.SubtitleFormat;
import net.filebot.subtitle.SubtitleNaming;
import net.filebot.web.Datasource;
import net.filebot.web.SortOrder; import net.filebot.web.SortOrder;
public class ArgumentBean { public class ArgumentBean {
@ -194,12 +207,61 @@ public class ArgumentBean {
return SortOrder.forName(order); return SortOrder.forName(order);
} }
public Locale getLocale() { public ExpressionFormat getExpressionFormat() throws Exception {
return new Locale(lang); return format == null ? null : new ExpressionFormat(format);
}
public ExpressionFilter getExpressionFilter() throws Exception {
return filter == null ? null : new ExpressionFilter(filter);
}
public FileFilter getExpressionFileFilter() throws Exception {
return filter == null ? null : new ExpressionFileFilter(filter);
}
public Datasource getDatasource() {
return db == null ? null : WebServices.getService(db);
}
public String getSearchQuery() {
return query == null || query.isEmpty() ? null : query;
}
public File getOutputPath() {
return output == null ? null : new File(output);
}
public File getAbsoluteOutputFolder() throws Exception {
return output == null ? null : new File(output).getCanonicalFile();
}
public SubtitleFormat getSubtitleOutputFormat() {
return output == null ? null : getSubtitleFormatByName(output);
}
public SubtitleNaming getSubtitleNamingFormat() {
return optional(format).map(SubtitleNaming::forName).orElse(SubtitleNaming.MATCH_VIDEO_ADD_LANGUAGE_TAG);
}
public HashType getOutputHashType() {
// support --output checksum.sfv
return optional(output).map(File::new).map(f -> getHashType(f)).orElseGet(() -> {
// support --format SFV
return optional(format).map(k -> getHashTypeByExtension(k)).orElse(HashType.SFV);
});
}
public Charset getEncoding() {
return encoding == null ? null : Charset.forName(encoding);
} }
public Language getLanguage() { public Language getLanguage() {
return Language.findLanguage(lang); // find language code for any input (en, eng, English, etc)
return optional(lang).map(Language::findLanguage).orElseThrow(error("Illegal language code", lang));
}
public boolean isStrict() {
return !nonStrict;
} }
public Level getLogLevel() { public Level getLogLevel() {
@ -226,4 +288,12 @@ public class ArgumentBean {
return buffer.toString(); return buffer.toString();
} }
private static <T> Optional<T> optional(T value) {
return Optional.ofNullable(value);
}
private static Supplier<CmdlineException> error(String message, Object value) {
return () -> new CmdlineException(message + ": " + value);
}
} }

View File

@ -5,16 +5,15 @@ import static net.filebot.util.ExceptionUtilities.*;
import static net.filebot.util.FileUtilities.*; import static net.filebot.util.FileUtilities.*;
import java.io.File; import java.io.File;
import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.logging.Level; import java.util.logging.Level;
import javax.script.Bindings; import javax.script.Bindings;
import javax.script.SimpleBindings; import javax.script.SimpleBindings;
import net.filebot.MediaTypes; import net.filebot.MediaTypes;
import net.filebot.StandardRenameAction;
public class ArgumentProcessor { public class ArgumentProcessor {
@ -57,47 +56,47 @@ public class ArgumentProcessor {
// print episode info // print episode info
if (args.list) { if (args.list) {
List<String> lines = cli.fetchEpisodeList(args.query, args.format, args.db, args.order, args.filter, args.lang); List<String> lines = cli.fetchEpisodeList(args.getDatasource(), args.getSearchQuery(), args.getExpressionFormat(), args.getExpressionFilter(), args.getSortOrder(), args.getLanguage().getLocale());
lines.forEach(System.out::println); lines.forEach(System.out::println);
return lines.isEmpty() ? 1 : 0; return lines.isEmpty() ? 1 : 0;
} }
// print media info // print media info
if (args.mediaInfo) { if (args.mediaInfo) {
List<String> lines = cli.getMediaInfo(args.getFiles(true), args.format, args.filter); List<String> lines = cli.getMediaInfo(args.getFiles(true), args.getExpressionFileFilter(), args.getExpressionFormat());
lines.forEach(System.out::println); lines.forEach(System.out::println);
return lines.isEmpty() ? 1 : 0; return lines.isEmpty() ? 1 : 0;
} }
// revert files // revert files
if (args.revert) { if (args.revert) {
List<File> files = cli.revert(args.getFiles(false), args.filter, "TEST".equalsIgnoreCase(args.action)); List<File> files = cli.revert(args.getFiles(false), args.getExpressionFileFilter(), args.getRenameAction());
return files.isEmpty() ? 1 : 0; return files.isEmpty() ? 1 : 0;
} }
// file operations // file operations
Collection<File> files = new LinkedHashSet<File>(args.getFiles(true)); Set<File> files = new LinkedHashSet<File>(args.getFiles(true));
if (args.extract) { if (args.extract) {
files.addAll(cli.extract(files, args.output, args.conflict, null, true)); files.addAll(cli.extract(files, args.getOutputPath(), args.getConflictAction(), null, true));
} }
if (args.getSubtitles) { if (args.getSubtitles) {
files.addAll(cli.getMissingSubtitles(files, args.db, args.query, args.lang, args.output, args.encoding, args.format, !args.nonStrict)); files.addAll(cli.getMissingSubtitles(files, args.getSearchQuery(), args.getLanguage(), args.getSubtitleOutputFormat(), args.getEncoding(), args.getSubtitleNamingFormat(), args.isStrict()));
} }
if (args.rename) { if (args.rename) {
cli.rename(files, StandardRenameAction.forName(args.action), args.conflict, args.output, args.format, args.db, args.query, args.order, args.filter, args.lang, !args.nonStrict); cli.rename(files, args.getRenameAction(), args.getConflictAction(), args.getAbsoluteOutputFolder(), args.getExpressionFormat(), args.getDatasource(), args.getSearchQuery(), args.getSortOrder(), args.getExpressionFilter(), args.getLanguage().getLocale(), args.isStrict());
} }
if (args.check) { if (args.check) {
// check verification file // check verification file
if (containsOnly(files, MediaTypes.getDefaultFilter("verification"))) { if (containsOnly(files, MediaTypes.getDefaultFilter("verification"))) {
if (!cli.check(files)) { if (!cli.check(files)) {
throw new Exception("Data corruption detected"); // one or more hashes mismatch throw new Exception("Data corruption detected"); // one or more hashes do not match
} }
} else { } else {
cli.compute(files, args.output, args.encoding); cli.compute(files, args.getOutputPath(), args.getOutputHashType(), args.getEncoding());
} }
} }

View File

@ -2,32 +2,42 @@ package net.filebot.cli;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.nio.charset.Charset;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import net.filebot.Language;
import net.filebot.RenameAction; import net.filebot.RenameAction;
import net.filebot.format.ExpressionFilter;
import net.filebot.format.ExpressionFormat;
import net.filebot.hash.HashType;
import net.filebot.subtitle.SubtitleFormat;
import net.filebot.subtitle.SubtitleNaming;
import net.filebot.web.Datasource;
import net.filebot.web.SortOrder;
public interface CmdlineInterface { public interface CmdlineInterface {
List<File> rename(Collection<File> files, RenameAction action, String conflict, String output, String format, String db, String query, String sortOrder, String filter, String lang, boolean strict) throws Exception; List<File> rename(Collection<File> files, RenameAction action, ConflictAction conflict, File output, ExpressionFormat format, Datasource db, String query, SortOrder order, ExpressionFilter filter, Locale locale, boolean strict) throws Exception;
List<File> rename(Map<File, File> renameMap, RenameAction renameAction, String conflict) throws Exception; List<File> rename(Map<File, File> rename, RenameAction action, ConflictAction conflict) throws Exception;
List<File> revert(Collection<File> files, String filter, boolean test) throws Exception; List<File> revert(Collection<File> files, FileFilter filter, RenameAction action) throws Exception;
List<File> getSubtitles(Collection<File> files, String db, String query, String lang, String output, String encoding, String format, boolean strict) throws Exception; List<File> getSubtitles(Collection<File> files, String query, Language language, SubtitleFormat output, Charset encoding, SubtitleNaming format, boolean strict) throws Exception;
List<File> getMissingSubtitles(Collection<File> files, String db, String query, String lang, String output, String encoding, String format, boolean strict) throws Exception; List<File> getMissingSubtitles(Collection<File> files, String query, Language language, SubtitleFormat output, Charset encoding, SubtitleNaming format, boolean strict) throws Exception;
boolean check(Collection<File> files) throws Exception; boolean check(Collection<File> files) throws Exception;
File compute(Collection<File> files, String output, String encoding) throws Exception; File compute(Collection<File> files, File output, HashType hash, Charset encoding) throws Exception;
List<String> fetchEpisodeList(String query, String format, String db, String sortOrder, String filter, String lang) throws Exception; List<String> fetchEpisodeList(Datasource db, String query, ExpressionFormat format, ExpressionFilter filter, SortOrder order, Locale locale) throws Exception;
List<String> getMediaInfo(Collection<File> files, String format, String filter) throws Exception; List<String> getMediaInfo(Collection<File> files, FileFilter filter, ExpressionFormat format) throws Exception;
List<File> extract(Collection<File> files, String output, String conflict, FileFilter filter, boolean forceExtractAll) throws Exception; List<File> extract(Collection<File> files, File output, ConflictAction conflict, FileFilter filter, boolean forceExtractAll) throws Exception;
} }

View File

@ -17,7 +17,6 @@ import static net.filebot.util.RegularExpressions.*;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.charset.Charset; import java.nio.charset.Charset;
@ -37,7 +36,6 @@ import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.function.Predicate;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -49,7 +47,6 @@ import net.filebot.RenameAction;
import net.filebot.StandardRenameAction; import net.filebot.StandardRenameAction;
import net.filebot.archive.Archive; import net.filebot.archive.Archive;
import net.filebot.archive.FileMapper; import net.filebot.archive.FileMapper;
import net.filebot.format.ExpressionFileFilter;
import net.filebot.format.ExpressionFilter; import net.filebot.format.ExpressionFilter;
import net.filebot.format.ExpressionFormat; import net.filebot.format.ExpressionFormat;
import net.filebot.format.MediaBindingBean; import net.filebot.format.MediaBindingBean;
@ -59,7 +56,6 @@ import net.filebot.hash.VerificationFileWriter;
import net.filebot.media.AutoDetection; import net.filebot.media.AutoDetection;
import net.filebot.media.AutoDetection.Group; import net.filebot.media.AutoDetection.Group;
import net.filebot.media.AutoDetection.Type; import net.filebot.media.AutoDetection.Type;
import net.filebot.media.MediaDetection;
import net.filebot.media.VideoQuality; import net.filebot.media.VideoQuality;
import net.filebot.media.XattrMetaInfoProvider; import net.filebot.media.XattrMetaInfoProvider;
import net.filebot.similarity.CommonSequenceMatcher; import net.filebot.similarity.CommonSequenceMatcher;
@ -93,30 +89,25 @@ import net.filebot.web.VideoHashSubtitleService;
public class CmdlineOperations implements CmdlineInterface { public class CmdlineOperations implements CmdlineInterface {
@Override @Override
public List<File> rename(Collection<File> files, RenameAction action, String conflict, String output, String formatExpression, String db, String query, String sortOrder, String filterExpression, String lang, boolean strict) throws Exception { public List<File> rename(Collection<File> files, RenameAction action, ConflictAction conflict, File output, ExpressionFormat format, Datasource db, String query, SortOrder order, ExpressionFilter filter, Locale locale, boolean strict) throws Exception {
ExpressionFormat format = (formatExpression != null) ? new ExpressionFormat(formatExpression) : null; // movie mode
ExpressionFilter filter = (filterExpression != null) ? new ExpressionFilter(filterExpression) : null; if (db instanceof MovieIdentificationService) {
File outputDir = (output != null && output.length() > 0) ? new File(output).getAbsoluteFile() : null; return renameMovie(files, action, conflict, output, format, (MovieIdentificationService) db, query, filter, locale, strict);
Locale locale = getLanguage(lang).getLocale();
ConflictAction conflictAction = ConflictAction.forName(conflict);
if (getMovieIdentificationService(db) != null) {
// movie mode
return renameMovie(files, action, conflictAction, outputDir, format, getMovieIdentificationService(db), query, filter, locale, strict);
} }
if (getEpisodeListProvider(db) != null) { // series mode
// tv series mode if (db instanceof EpisodeListProvider) {
return renameSeries(files, action, conflictAction, outputDir, format, getEpisodeListProvider(db), query, SortOrder.forName(sortOrder), filter, locale, strict); return renameSeries(files, action, conflict, output, format, (EpisodeListProvider) db, query, order, filter, locale, strict);
} }
if (getMusicIdentificationService(db) != null) { // music mode
// music mode if (db instanceof MusicIdentificationService) {
return renameMusic(files, action, conflictAction, outputDir, format, getMusicIdentificationService(db)); return renameMusic(files, action, conflict, output, format, (MusicIdentificationService) db);
} }
if (XattrMetaData.getIdentifier().equalsIgnoreCase(db)) { // generic file / xattr mode
return renameFiles(files, action, conflictAction, outputDir, format, XattrMetaData, filter, strict); if (db instanceof XattrMetaInfoProvider) {
return renameFiles(files, action, conflict, output, format, (XattrMetaInfoProvider) db, filter, strict);
} }
// auto-detect mode for each fileset // auto-detect mode for each fileset
@ -128,16 +119,16 @@ public class CmdlineOperations implements CmdlineInterface {
for (Type key : it.getKey().types()) { for (Type key : it.getKey().types()) {
switch (key) { switch (key) {
case Movie: case Movie:
results.addAll(renameMovie(it.getValue(), action, conflictAction, outputDir, format, TheMovieDB, query, filter, locale, strict)); results.addAll(renameMovie(it.getValue(), action, conflict, output, format, TheMovieDB, query, filter, locale, strict));
break; break;
case Series: case Series:
results.addAll(renameSeries(it.getValue(), action, conflictAction, outputDir, format, TheTVDB, query, SortOrder.forName(sortOrder), filter, locale, strict)); results.addAll(renameSeries(it.getValue(), action, conflict, output, format, TheTVDB, query, order, filter, locale, strict));
break; break;
case Anime: case Anime:
results.addAll(renameSeries(it.getValue(), action, conflictAction, outputDir, format, AniDB, query, SortOrder.forName(sortOrder), filter, locale, strict)); results.addAll(renameSeries(it.getValue(), action, conflict, output, format, AniDB, query, order, filter, locale, strict));
break; break;
case Music: case Music:
results.addAll(renameMusic(it.getValue(), action, conflictAction, outputDir, format, MediaInfoID3, AcoustID)); results.addAll(renameMusic(it.getValue(), action, conflict, output, format, MediaInfoID3, AcoustID));
break; break;
} }
} }
@ -146,13 +137,17 @@ public class CmdlineOperations implements CmdlineInterface {
} }
} }
if (results.isEmpty()) {
throw new CmdlineException("Failed to identify or process any files");
}
return results; return results;
} }
@Override @Override
public List<File> rename(Map<File, File> renameMap, RenameAction renameAction, String conflict) throws Exception { public List<File> rename(Map<File, File> renameMap, RenameAction renameAction, ConflictAction conflict) throws Exception {
// generic rename function that can be passed any set of files // generic rename function that can be passed any set of files
return renameAll(renameMap, renameAction, ConflictAction.forName(conflict), null); return renameAll(renameMap, renameAction, conflict, null);
} }
public List<File> renameSeries(Collection<File> files, RenameAction renameAction, ConflictAction conflictAction, File outputDir, ExpressionFormat format, EpisodeListProvider db, String query, SortOrder sortOrder, ExpressionFilter filter, Locale locale, boolean strict) throws Exception { public List<File> renameSeries(Collection<File> files, RenameAction renameAction, ConflictAction conflictAction, File outputDir, ExpressionFormat format, EpisodeListProvider db, String query, SortOrder sortOrder, ExpressionFilter filter, Locale locale, boolean strict) throws Exception {
@ -658,7 +653,7 @@ public class CmdlineOperations implements CmdlineInterface {
return new ArrayList<File>(renameLog.values()); return new ArrayList<File>(renameLog.values());
} }
private static File nextAvailableIndexedName(File file) { protected static File nextAvailableIndexedName(File file) {
File parent = file.getParentFile(); File parent = file.getParentFile();
String name = getName(file); String name = getName(file);
String ext = getExtension(file); String ext = getExtension(file);
@ -666,17 +661,7 @@ public class CmdlineOperations implements CmdlineInterface {
} }
@Override @Override
public List<File> getSubtitles(Collection<File> files, String db, String query, String languageName, String output, String csn, String format, boolean strict) throws Exception { public List<File> getSubtitles(Collection<File> files, String query, Language language, SubtitleFormat output, Charset encoding, SubtitleNaming format, boolean strict) throws Exception {
Language language = getLanguage(languageName);
SubtitleNaming naming = getSubtitleNaming(format);
// use all or only selected subtitle services
Predicate<Datasource> serviceFilter = service -> db == null ? true : db.contains(service.getName()) || db.contains(service.getIdentifier());
// when rewriting subtitles to target format an encoding must be defined, default to UTF-8
Charset outputEncoding = csn != null ? Charset.forName(csn) : output != null ? UTF_8 : null;
SubtitleFormat outputFormat = output != null ? getSubtitleFormatByName(output) : null;
// ignore anything that is not a video // ignore anything that is not a video
files = filter(files, VIDEO_FILES); files = filter(files, VIDEO_FILES);
@ -696,14 +681,14 @@ public class CmdlineOperations implements CmdlineInterface {
// lookup subtitles by hash // lookup subtitles by hash
for (VideoHashSubtitleService service : getVideoHashSubtitleServices(language.getLocale())) { for (VideoHashSubtitleService service : getVideoHashSubtitleServices(language.getLocale())) {
if (remainingVideos.isEmpty() || !serviceFilter.test(service) || !requireLogin(service)) { if (remainingVideos.isEmpty() || !requireLogin(service)) {
continue; continue;
} }
try { try {
log.fine("Looking up subtitles by hash via " + service.getName()); log.fine("Looking up subtitles by hash via " + service.getName());
Map<File, List<SubtitleDescriptor>> options = lookupSubtitlesByHash(service, remainingVideos, language.getName(), false, strict); Map<File, List<SubtitleDescriptor>> options = lookupSubtitlesByHash(service, remainingVideos, language.getName(), false, strict);
Map<File, File> downloads = downloadSubtitleBatch(service, options, outputFormat, outputEncoding, naming); Map<File, File> downloads = downloadSubtitleBatch(service, options, output, encoding, format);
remainingVideos.removeAll(downloads.keySet()); remainingVideos.removeAll(downloads.keySet());
subtitleFiles.addAll(downloads.values()); subtitleFiles.addAll(downloads.values());
} catch (Exception e) { } catch (Exception e) {
@ -711,15 +696,15 @@ public class CmdlineOperations implements CmdlineInterface {
} }
} }
for (SubtitleProvider service : getSubtitleProviders()) { for (SubtitleProvider service : getSubtitleProviders(language.getLocale())) {
if (strict || remainingVideos.isEmpty() || !serviceFilter.test(service) || !requireLogin(service)) { if (strict || remainingVideos.isEmpty() || !requireLogin(service)) {
continue; continue;
} }
try { try {
log.fine(format("Looking up subtitles by name via %s", service.getName())); log.fine(format("Looking up subtitles by name via %s", service.getName()));
Map<File, List<SubtitleDescriptor>> options = findSubtitlesByName(service, remainingVideos, language.getName(), query, false, strict); Map<File, List<SubtitleDescriptor>> options = findSubtitlesByName(service, remainingVideos, language.getName(), query, false, strict);
Map<File, File> downloads = downloadSubtitleBatch(service, options, outputFormat, outputEncoding, naming); Map<File, File> downloads = downloadSubtitleBatch(service, options, output, encoding, format);
remainingVideos.removeAll(downloads.keySet()); remainingVideos.removeAll(downloads.keySet());
subtitleFiles.addAll(downloads.values()); subtitleFiles.addAll(downloads.values());
} catch (Exception e) { } catch (Exception e) {
@ -735,7 +720,7 @@ public class CmdlineOperations implements CmdlineInterface {
return subtitleFiles; return subtitleFiles;
} }
private static boolean requireLogin(Object service) { protected static boolean requireLogin(Object service) {
if (service instanceof OpenSubtitlesClient) { if (service instanceof OpenSubtitlesClient) {
OpenSubtitlesClient osdb = (OpenSubtitlesClient) service; OpenSubtitlesClient osdb = (OpenSubtitlesClient) service;
if (osdb.isAnonymous()) { if (osdb.isAnonymous()) {
@ -746,47 +731,38 @@ public class CmdlineOperations implements CmdlineInterface {
} }
@Override @Override
public List<File> getMissingSubtitles(Collection<File> files, String db, String query, String languageName, String output, String csn, String format, boolean strict) throws Exception { public List<File> getMissingSubtitles(Collection<File> files, String query, Language language, SubtitleFormat output, Charset encoding, SubtitleNaming format, boolean strict) throws Exception {
// sanity check
for (File f : files) {
if (!f.exists()) {
throw new FileNotFoundException(f.toString());
}
}
List<File> videoFiles = filter(filter(files, VIDEO_FILES), new FileFilter() { List<File> videoFiles = filter(filter(files, VIDEO_FILES), new FileFilter() {
// save time on repeating filesystem calls // save time on repeating filesystem calls
private Map<File, List<File>> cache = new HashMap<File, List<File>>(); private Map<File, List<File>> cache = new HashMap<File, List<File>>();
private SubtitleNaming naming = getSubtitleNaming(format);
// get language code suffix for given language (.eng)
private String languageCode = Language.getStandardLanguageCode(getLanguage(languageName).getName());
public boolean matchesLanguageCode(File f) { public boolean matchesLanguageCode(File f) {
Locale languageSuffix = MediaDetection.releaseInfo.getSubtitleLanguageTag(getName(f)); Language languageSuffix = Language.getLanguage(releaseInfo.getSubtitleLanguageTag(getName(f)));
Language language = Language.getLanguage(languageSuffix); if (languageSuffix != null) {
if (language != null) { return languageSuffix.getCode().equals(language.getCode());
return language.getISO3().equalsIgnoreCase(languageCode);
} }
return false; return false;
} }
@Override @Override
public boolean accept(File video) { public boolean accept(File video) {
if (!video.isFile()) {
return false;
}
List<File> subtitleFiles = cache.computeIfAbsent(video.getParentFile(), parent -> { List<File> subtitleFiles = cache.computeIfAbsent(video.getParentFile(), parent -> {
return getChildren(parent, SUBTITLE_FILES); return getChildren(parent, SUBTITLE_FILES);
}); });
// can't tell which subtitle belongs to which file -> if any subtitles exist skip the whole folder // can't tell which subtitle belongs to which file -> if any subtitles exist skip the whole folder
if (naming == SubtitleNaming.ORIGINAL) { if (format == SubtitleNaming.ORIGINAL) {
return subtitleFiles.size() == 0; return subtitleFiles.size() == 0;
} }
return subtitleFiles.stream().allMatch(f -> { return subtitleFiles.stream().allMatch(f -> {
if (isDerived(f, video)) { if (isDerived(f, video)) {
return naming != SubtitleNaming.MATCH_VIDEO && !matchesLanguageCode(f); return format != SubtitleNaming.MATCH_VIDEO && !matchesLanguageCode(f);
} }
return true; return true;
}); });
@ -798,16 +774,7 @@ public class CmdlineOperations implements CmdlineInterface {
return emptyList(); return emptyList();
} }
return getSubtitles(videoFiles, db, query, languageName, output, csn, format, strict); return getSubtitles(videoFiles, query, language, output, encoding, format, strict);
}
private SubtitleNaming getSubtitleNaming(String format) {
SubtitleNaming naming = SubtitleNaming.forName(format);
if (naming != null) {
return naming;
} else {
return SubtitleNaming.MATCH_VIDEO_ADD_LANGUAGE_TAG;
}
} }
private Map<File, File> downloadSubtitleBatch(Datasource service, Map<File, List<SubtitleDescriptor>> subtitles, SubtitleFormat outputFormat, Charset outputEncoding, SubtitleNaming naming) { private Map<File, File> downloadSubtitleBatch(Datasource service, Map<File, List<SubtitleDescriptor>> subtitles, SubtitleFormat outputFormat, Charset outputEncoding, SubtitleNaming naming) {
@ -834,19 +801,25 @@ public class CmdlineOperations implements CmdlineInterface {
MemoryFile subtitleFile = fetchSubtitle(descriptor); MemoryFile subtitleFile = fetchSubtitle(descriptor);
// subtitle filename is based on movie filename // subtitle filename is based on movie filename
String ext = getExtension(subtitleFile.getName()); String extension = getExtension(subtitleFile.getName());
ByteBuffer data = subtitleFile.getData(); ByteBuffer data = subtitleFile.getData();
if (outputFormat != null || outputEncoding != null) { if (outputFormat != null || outputEncoding != null) {
// adjust extension of the output file
if (outputFormat != null) { if (outputFormat != null) {
ext = outputFormat.getFilter().extension(); // adjust extension of the output file extension = outputFormat.getFilter().extension();
} }
log.finest(format("Export [%s] as [%s / %s]", subtitleFile.getName(), outputFormat, outputEncoding.displayName(Locale.ROOT))); // default to UTF-8 if no other encoding is given
if (outputEncoding == null) {
outputEncoding = UTF_8;
}
log.finest(format("Export [%s] as [%s / %s]", subtitleFile.getName(), outputFormat, outputEncoding));
data = exportSubtitles(subtitleFile, outputFormat, 0, outputEncoding); data = exportSubtitles(subtitleFile, outputFormat, 0, outputEncoding);
} }
File destination = new File(movieFile.getParentFile(), naming.format(movieFile, descriptor, ext)); File destination = new File(movieFile.getParentFile(), naming.format(movieFile, descriptor, extension));
log.info(format("Writing [%s] to [%s]", subtitleFile.getName(), destination.getName())); log.info(format("Writing [%s] to [%s]", subtitleFile.getName(), destination.getName()));
writeFile(data, destination); writeFile(data, destination);
@ -893,18 +866,6 @@ public class CmdlineOperations implements CmdlineInterface {
return probableMatches.size() <= 5 ? probableMatches : probableMatches.subList(0, 5); // trust that the correct match is in the Top 3 return probableMatches.size() <= 5 ? probableMatches : probableMatches.subList(0, 5); // trust that the correct match is in the Top 3
} }
private Language getLanguage(String lang) throws Exception {
// try to look up by language code
Language language = Language.findLanguage(lang);
if (language == null) {
// unable to lookup language
throw new CmdlineException("Illegal language code: " + lang);
}
return language;
}
@Override @Override
public boolean check(Collection<File> files) throws Exception { public boolean check(Collection<File> files) throws Exception {
// only check existing hashes // only check existing hashes
@ -918,10 +879,14 @@ public class CmdlineOperations implements CmdlineInterface {
} }
@Override @Override
public File compute(Collection<File> files, String output, String csn) throws Exception { public File compute(Collection<File> files, File output, HashType hash, Charset encoding) throws Exception {
// ignore folders and any sort of special files // ignore folders and any sort of special files
files = filter(files, FILES); files = filter(files, FILES);
if (files.isEmpty()) {
throw new CmdlineException("No files: " + files);
}
// find common parent folder of all files // find common parent folder of all files
File[] fileList = files.toArray(new File[0]); File[] fileList = files.toArray(new File[0]);
File[][] pathArray = new File[fileList.length][]; File[][] pathArray = new File[fileList.length][];
@ -933,38 +898,22 @@ public class CmdlineOperations implements CmdlineInterface {
File[] common = csm.matchFirstCommonSequence(pathArray); File[] common = csm.matchFirstCommonSequence(pathArray);
if (common == null) { if (common == null) {
throw new CmdlineException("Paths must be on the same filesystem: " + files); throw new CmdlineException("All paths must be on the same filesystem: " + files);
} }
// last element in the common sequence must be the root folder // last element in the common sequence must be the root folder
File root = common[common.length - 1]; File root = common[common.length - 1];
// create verification file if (output == null) {
File outputFile; output = new File(root, root.getName() + '.' + hash.getFilter().extension());
HashType hashType; } else if (!output.isAbsolute()) {
output = new File(root, output.getPath());
if (output != null && getExtension(output) != null) {
// use given filename
hashType = getHashTypeByExtension(getExtension(output));
outputFile = new File(root, output);
} else {
// auto-select the filename based on folder and type
hashType = (output != null) ? getHashTypeByExtension(output) : HashType.SFV;
outputFile = new File(root, root.getName() + "." + hashType.getFilter().extension());
} }
if (hashType == null) { log.info(format("Compute %s hash for %s files [%s]", hash, files.size(), output));
throw new CmdlineException("Illegal output type: " + output); compute(root, files, output, hash, encoding);
}
if (files.isEmpty()) { return output;
throw new CmdlineException("No files: " + files);
}
log.info(format("Compute %s hash for %s files [%s]", hashType, files.size(), outputFile));
compute(root.getPath(), files, outputFile, hashType, csn);
return outputFile;
} }
private boolean check(File verificationFile, File root) throws Exception { private boolean check(File verificationFile, File root) throws Exception {
@ -1004,16 +953,16 @@ public class CmdlineOperations implements CmdlineInterface {
return status; return status;
} }
private void compute(String root, Collection<File> files, File outputFile, HashType hashType, String csn) throws IOException, Exception { private void compute(File root, Collection<File> files, File outputFile, HashType hashType, Charset encoding) throws IOException, Exception {
// compute hashes recursively and write to file // compute hashes recursively and write to file
VerificationFileWriter out = new VerificationFileWriter(outputFile, hashType.getFormat(), csn != null ? csn : "UTF-8"); VerificationFileWriter out = new VerificationFileWriter(outputFile, hashType.getFormat(), encoding != null ? encoding : UTF_8);
try { try {
for (File it : files) { for (File it : files) {
if (it.isHidden() || MediaTypes.getDefaultFilter("verification").accept(it)) if (it.isHidden() || MediaTypes.getDefaultFilter("verification").accept(it))
continue; continue;
String relativePath = normalizePathSeparators(it.getPath().replace(root, "")).substring(1); String relativePath = normalizePathSeparators(it.getPath().substring(root.getPath().length() + 1)); // skip root and first slash
String hash = computeHash(it, hashType); String hash = computeHash(it, hashType);
log.info(format("%s %s", hash, relativePath)); log.info(format("%s %s", hash, relativePath));
@ -1028,17 +977,13 @@ public class CmdlineOperations implements CmdlineInterface {
} }
@Override @Override
public List<String> fetchEpisodeList(String query, String expression, String db, String sortOrderName, String filterExpression, String languageName) throws Exception { public List<String> fetchEpisodeList(Datasource db, String query, ExpressionFormat format, ExpressionFilter filter, SortOrder order, Locale locale) throws Exception {
if (query == null || query.isEmpty()) { if (query == null) {
throw new IllegalArgumentException("query is not defined"); throw new IllegalArgumentException("query is not defined");
} }
// find series on the web and fetch episode list // find series on the web and fetch episode list
ExpressionFormat format = expression == null ? null : new ExpressionFormat(expression); EpisodeListProvider service = db instanceof EpisodeListProvider ? (EpisodeListProvider) db : TheTVDB;
ExpressionFilter filter = filterExpression == null ? null : new ExpressionFilter(filterExpression);
EpisodeListProvider service = db == null ? TheTVDB : getEpisodeListProvider(db);
SortOrder sortOrder = SortOrder.forName(sortOrderName);
Locale locale = getLanguage(languageName).getLocale();
// search and select search result // search and select search result
List<SearchResult> options = selectSearchResult(query, service.search(query, locale), false, false); List<SearchResult> options = selectSearchResult(query, service.search(query, locale), false, false);
@ -1047,7 +992,7 @@ public class CmdlineOperations implements CmdlineInterface {
} }
// fetch episodes and apply filter // fetch episodes and apply filter
List<Episode> episodes = applyExpressionFilter(service.getEpisodeList(options.get(0), sortOrder, locale), filter); List<Episode> episodes = applyExpressionFilter(service.getEpisodeList(options.get(0), order, locale), filter);
Map<File, Episode> context = new EntryList<File, Episode>(null, episodes); Map<File, Episode> context = new EntryList<File, Episode>(null, episodes);
return episodes.stream().map(episode -> { return episodes.stream().map(episode -> {
@ -1056,49 +1001,52 @@ public class CmdlineOperations implements CmdlineInterface {
} }
@Override @Override
public List<String> getMediaInfo(Collection<File> files, String format, String filter) throws Exception { public List<String> getMediaInfo(Collection<File> files, FileFilter filter, ExpressionFormat format) throws Exception {
ExpressionFormat formatter = new ExpressionFormat(format != null && format.length() > 0 ? format : "{fn} [{resolution} {vc} {channels} {ac} {minutes}m]"); List<File> selection = filter(files, FILES);
List<File> selection = filter(files, filter == null || filter.isEmpty() ? f -> true : new ExpressionFileFilter(new ExpressionFilter(filter), false));
// apply custom filter
if (filter != null) {
selection = filter(selection, filter);
}
// default expression format if not set
ExpressionFormat formatter = format != null ? format : new ExpressionFormat("{fn} [{resolution} {vc} {channels} {ac} {minutes}m]");
// lazy format
return new FunctionList<File, String>(selection, f -> formatter.format(new MediaBindingBean(xattr.getMetaInfo(f), f, null))); return new FunctionList<File, String>(selection, f -> formatter.format(new MediaBindingBean(xattr.getMetaInfo(f), f, null)));
} }
@Override @Override
public List<File> revert(Collection<File> files, String filter, boolean test) throws Exception { public List<File> revert(Collection<File> files, FileFilter filter, RenameAction action) throws Exception {
if (files.isEmpty()) { if (files.isEmpty()) {
throw new CmdlineException("Expecting at least one input path"); throw new CmdlineException("Expecting at least one input path");
} }
FileFilter fileFilter = filter == null || filter.isEmpty() ? f -> true : new ExpressionFileFilter(new ExpressionFilter(filter), false);
Set<File> whitelist = new HashSet<File>(files); Set<File> whitelist = new HashSet<File>(files);
Map<File, File> history = HistorySpooler.getInstance().getCompleteHistory().getRenameMap(); Map<File, File> history = HistorySpooler.getInstance().getCompleteHistory().getRenameMap();
return history.entrySet().stream().filter(it -> { return history.entrySet().stream().filter(it -> {
File original = it.getKey(); File original = it.getKey();
File current = it.getValue(); File current = it.getValue();
return Stream.of(current, original).flatMap(f -> listPath(f).stream()).anyMatch(whitelist::contains) && current.exists() && fileFilter.accept(current); return Stream.of(current, original).flatMap(f -> listPath(f).stream()).anyMatch(whitelist::contains) && current.exists() && (filter == null || filter.accept(current));
}).map(it -> { }).map(it -> {
File original = it.getKey(); File original = it.getKey();
File current = it.getValue(); File current = it.getValue();
log.info(format("Revert [%s] to [%s]", current, original)); log.info(format("Revert [%s] to [%s]", current, original));
if (test) { if (action.canRevert()) {
return original; try {
} return StandardRenameAction.revert(current, original);
} catch (Exception e) {
try { log.warning("Failed to revert file: " + e);
return StandardRenameAction.revert(current, original); }
} catch (Exception e) {
log.warning(format("Failed to revert file: %s", e.getMessage()));
return null;
} }
return null;
}).filter(Objects::nonNull).collect(toList()); }).filter(Objects::nonNull).collect(toList());
} }
@Override @Override
public List<File> extract(Collection<File> files, String output, String conflict, FileFilter filter, boolean forceExtractAll) throws Exception { public List<File> extract(Collection<File> files, File output, ConflictAction conflict, FileFilter filter, boolean forceExtractAll) throws Exception {
ConflictAction conflictAction = ConflictAction.forName(conflict);
// only keep single-volume archives or first part of multi-volume archives // only keep single-volume archives or first part of multi-volume archives
List<File> archiveFiles = filter(files, Archive.VOLUME_ONE_FILTER); List<File> archiveFiles = filter(files, Archive.VOLUME_ONE_FILTER);
List<File> extractedFiles = new ArrayList<File>(); List<File> extractedFiles = new ArrayList<File>();
@ -1106,11 +1054,11 @@ public class CmdlineOperations implements CmdlineInterface {
for (File file : archiveFiles) { for (File file : archiveFiles) {
Archive archive = Archive.open(file); Archive archive = Archive.open(file);
try { try {
File outputFolder = new File(output != null ? output : getName(file)); File outputFolder = output;
if (!outputFolder.isAbsolute()) {
outputFolder = new File(file.getParentFile(), outputFolder.getPath()); if (outputFolder == null || !outputFolder.isAbsolute()) {
outputFolder = new File(file.getParentFile(), outputFolder == null ? getName(file) : outputFolder.getPath()).getCanonicalFile();
} }
outputFolder = outputFolder.getCanonicalFile(); // normalize weird paths
log.info(format("Read archive [%s] and extract to [%s]", file.getName(), outputFolder)); log.info(format("Read archive [%s] and extract to [%s]", file.getName(), outputFolder));
FileMapper outputMapper = new FileMapper(outputFolder); FileMapper outputMapper = new FileMapper(outputFolder);
@ -1135,14 +1083,14 @@ public class CmdlineOperations implements CmdlineInterface {
boolean skip = true; boolean skip = true;
for (FileInfo future : filter == null || forceExtractAll ? outputMapping : selection) { for (FileInfo future : filter == null || forceExtractAll ? outputMapping : selection) {
if (conflictAction == ConflictAction.AUTO) { if (conflict == ConflictAction.AUTO) {
skip &= (future.toFile().exists() && future.getLength() == future.toFile().length()); skip &= (future.toFile().exists() && future.getLength() == future.toFile().length());
} else { } else {
skip &= (future.toFile().exists()); skip &= (future.toFile().exists());
} }
} }
if (!skip || conflictAction == ConflictAction.OVERRIDE) { if (!skip || conflict == ConflictAction.OVERRIDE) {
if (filter == null || forceExtractAll) { if (filter == null || forceExtractAll) {
log.finest("Extracting files " + outputMapping); log.finest("Extracting files " + outputMapping);

View File

@ -6,14 +6,14 @@ import static net.filebot.media.XattrMetaInfo.*;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import javax.script.ScriptException;
public class ExpressionFileFilter implements FileFilter { public class ExpressionFileFilter implements FileFilter {
private final ExpressionFilter filter; private ExpressionFilter filter;
private final boolean error;
public ExpressionFileFilter(ExpressionFilter filter, boolean error) { public ExpressionFileFilter(String expression) throws ScriptException {
this.filter = filter; this.filter = new ExpressionFilter(expression);
this.error = error;
} }
public ExpressionFilter getExpressionFilter() { public ExpressionFilter getExpressionFilter() {
@ -25,9 +25,9 @@ public class ExpressionFileFilter implements FileFilter {
try { try {
return filter.matches(new MediaBindingBean(xattr.getMetaInfo(f), f, null)); return filter.matches(new MediaBindingBean(xattr.getMetaInfo(f), f, null));
} catch (Exception e) { } catch (Exception e) {
debug.warning(format("Expression failed: %s", e)); debug.warning("Filter expression failed: " + e);
return error;
} }
return false;
} }
} }

View File

@ -24,7 +24,6 @@ public enum HashType {
public ExtensionFileFilter getFilter() { public ExtensionFileFilter getFilter() {
return MediaTypes.getDefaultFilter("verification/sfv"); return MediaTypes.getDefaultFilter("verification/sfv");
} }
}, },
MD5 { MD5 {
@ -44,7 +43,6 @@ public enum HashType {
public ExtensionFileFilter getFilter() { public ExtensionFileFilter getFilter() {
return MediaTypes.getDefaultFilter("verification/md5sum"); return MediaTypes.getDefaultFilter("verification/md5sum");
} }
}, },
SHA1 { SHA1 {
@ -69,7 +67,6 @@ public enum HashType {
public String toString() { public String toString() {
return "SHA1"; return "SHA1";
} }
}, },
SHA256 { SHA256 {
@ -94,7 +91,6 @@ public enum HashType {
public String toString() { public String toString() {
return "SHA2"; return "SHA2";
} }
}, },
ED2K { ED2K {

View File

@ -1,28 +1,28 @@
package net.filebot.hash; package net.filebot.hash;
import java.io.BufferedWriter;
import java.io.Closeable; import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.Date; import java.util.Date;
import net.filebot.Settings; import net.filebot.Settings;
public class VerificationFileWriter implements Closeable { public class VerificationFileWriter implements Closeable {
protected PrintWriter out; protected PrintWriter out;
protected VerificationFormat format; protected VerificationFormat format;
public VerificationFileWriter(File file, VerificationFormat format, Charset charset) throws IOException {
public VerificationFileWriter(File file, VerificationFormat format, String charset) throws IOException { this(new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), charset)), false), format, charset);
this(new PrintWriter(file, charset), format, charset);
} }
public VerificationFileWriter(PrintWriter out, VerificationFormat format, Charset charset) {
public VerificationFileWriter(PrintWriter out, VerificationFormat format, String charset) {
this.out = out; this.out = out;
this.format = format; this.format = format;
@ -30,19 +30,16 @@ public class VerificationFileWriter implements Closeable {
writeHeader(charset); writeHeader(charset);
} }
protected void writeHeader(Charset charset) {
protected void writeHeader(String charset) {
out.format("; Generated by %s %s on %tF at %<tT%n", Settings.getApplicationName(), Settings.getApplicationVersion(), new Date()); out.format("; Generated by %s %s on %tF at %<tT%n", Settings.getApplicationName(), Settings.getApplicationVersion(), new Date());
out.format("; charset=%s%n", charset); out.format("; charset=%s%n", charset);
out.format(";%n"); out.format(";%n");
} }
public void write(String path, String hash) { public void write(String path, String hash) {
out.format("%s%n", format.format(path, hash)); out.format("%s%n", format.format(path, hash));
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
out.close(); out.close();

View File

@ -57,7 +57,7 @@ public class Preset {
} }
public ExpressionFileFilter getIncludeFilter() { public ExpressionFileFilter getIncludeFilter() {
return getInputFolder() == null ? null : getValue(includes, expression -> new ExpressionFileFilter(new ExpressionFilter(expression), false)); return getInputFolder() == null ? null : getValue(includes, expression -> new ExpressionFileFilter(expression));
} }
public ExpressionFormat getFormat() { public ExpressionFormat getFormat() {

View File

@ -1,6 +1,7 @@
package net.filebot.ui.sfv; package net.filebot.ui.sfv;
import static java.nio.charset.StandardCharsets.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -11,35 +12,29 @@ import net.filebot.hash.VerificationFileWriter;
import net.filebot.ui.transfer.TextFileExportHandler; import net.filebot.ui.transfer.TextFileExportHandler;
import net.filebot.util.FileUtilities; import net.filebot.util.FileUtilities;
class ChecksumTableExportHandler extends TextFileExportHandler { class ChecksumTableExportHandler extends TextFileExportHandler {
private final ChecksumTableModel model; private final ChecksumTableModel model;
public ChecksumTableExportHandler(ChecksumTableModel model) { public ChecksumTableExportHandler(ChecksumTableModel model) {
this.model = model; this.model = model;
} }
@Override @Override
public boolean canExport() { public boolean canExport() {
return model.getRowCount() > 0 && defaultColumn() != null; return model.getRowCount() > 0 && defaultColumn() != null;
} }
@Override @Override
public void export(PrintWriter out) { public void export(PrintWriter out) {
export(new VerificationFileWriter(out, model.getHashType().getFormat(), "UTF-8"), defaultColumn(), model.getHashType()); export(new VerificationFileWriter(out, model.getHashType().getFormat(), UTF_8), defaultColumn(), model.getHashType());
} }
@Override @Override
public String getDefaultFileName() { public String getDefaultFileName() {
return getDefaultFileName(defaultColumn()); return getDefaultFileName(defaultColumn());
} }
protected File defaultColumn() { protected File defaultColumn() {
// select first column that is not a verification file column // select first column that is not a verification file column
for (File root : model.getChecksumColumns()) { for (File root : model.getChecksumColumns()) {
@ -50,9 +45,8 @@ class ChecksumTableExportHandler extends TextFileExportHandler {
return null; return null;
} }
public void export(File file, File column) throws IOException { public void export(File file, File column) throws IOException {
VerificationFileWriter writer = new VerificationFileWriter(file, model.getHashType().getFormat(), "UTF-8"); VerificationFileWriter writer = new VerificationFileWriter(file, model.getHashType().getFormat(), UTF_8);
try { try {
export(writer, column, model.getHashType()); export(writer, column, model.getHashType());
@ -61,7 +55,6 @@ class ChecksumTableExportHandler extends TextFileExportHandler {
} }
} }
public void export(VerificationFileWriter out, File column, HashType type) { public void export(VerificationFileWriter out, File column, HashType type) {
for (ChecksumRow row : model.rows()) { for (ChecksumRow row : model.rows()) {
ChecksumCell cell = row.getChecksum(column); ChecksumCell cell = row.getChecksum(column);
@ -76,7 +69,6 @@ class ChecksumTableExportHandler extends TextFileExportHandler {
} }
} }
public String getDefaultFileName(File column) { public String getDefaultFileName(File column) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();

View File

@ -124,15 +124,18 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
private final SubtitleDropTarget downloadDropTarget = new SubtitleDropTarget.Download() { private final SubtitleDropTarget downloadDropTarget = new SubtitleDropTarget.Download() {
public Locale getLocale() {
return languageComboBox.getModel().getSelectedItem() == ALL_LANGUAGES ? Locale.ROOT : languageComboBox.getModel().getSelectedItem().getLocale();
}
@Override @Override
public VideoHashSubtitleService[] getVideoHashSubtitleServices() { public VideoHashSubtitleService[] getVideoHashSubtitleServices() {
Locale locale = languageComboBox.getModel().getSelectedItem() == ALL_LANGUAGES ? Locale.ROOT : languageComboBox.getModel().getSelectedItem().getLocale(); return WebServices.getVideoHashSubtitleServices(getLocale());
return WebServices.getVideoHashSubtitleServices(locale);
} }
@Override @Override
public SubtitleProvider[] getSubtitleProviders() { public SubtitleProvider[] getSubtitleProviders() {
return WebServices.getSubtitleProviders(); return WebServices.getSubtitleProviders(getLocale());
} }
@Override @Override
@ -182,7 +185,7 @@ public class SubtitlePanel extends AbstractSearchPanel<SubtitleProvider, Subtitl
@Override @Override
protected SubtitleProvider[] getSearchEngines() { protected SubtitleProvider[] getSearchEngines() {
return WebServices.getSubtitleProviders(); return WebServices.getSubtitleProviders(getLocale());
} }
@Override @Override