Add language validation & refactor config initialization

This commit is contained in:
2022-04-18 15:07:56 +02:00
parent c2f32a30ce
commit a606c50637
10 changed files with 730 additions and 152 deletions

View File

@@ -4,64 +4,12 @@ import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.MkvFileCollector;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.MkvFileProcessor;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.cli.*;
import java.util.List;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty.*;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.CommandLineOptionsUtil.optionOf;
import static java.lang.Integer.parseInt;
@Log4j2
public class Main {
public static void main(String[] args) {
initConfig(args);
Config.getInstance().initConfig(args);
AttributeUpdaterKernel kernel = new AttributeUpdaterKernel(new MkvFileCollector(), new MkvFileProcessor());
kernel.execute();
}
private static void initConfig(String[] args) {
Options options = initOptions();
CommandLineParser parser = new DefaultParser();
HelpFormatter formatter = new HelpFormatter();
try {
CommandLine cmd = parser.parse(options, args);
if (cmd.hasOption("help")) {
formatter.printHelp("java -jar MKVAudioSubtitlesChanger.jar -p <path_to_library>", options);
System.exit(0);
}
Config config = Config.getInstance();
config.loadConfig(cmd.getOptionValue(CONFIG_PATH.prop(), "config.yaml")); // use cmd input
config.setLibraryPath(cmd.getOptionValue("library"));
config.setSafeMode(cmd.hasOption("safe-mode"));
if (cmd.hasOption("threads")) config.setThreadCount(parseInt(cmd.getOptionValue("threads")));
if (cmd.hasOption(FORCED_KEYWORDS.prop()))
config.getForcedKeywords().addAll(List.of(cmd.getOptionValues(FORCED_KEYWORDS.prop())));
if (cmd.hasOption(EXCLUDE_DIRECTORY.prop()))
config.getExcludedDirectories().addAll(List.of(cmd.getOptionValues(EXCLUDE_DIRECTORY.prop())));
if (cmd.hasOption(INCLUDE_PATTERN.prop())) {
config.setIncludePattern(Config.compilePattern(cmd.getOptionValue(INCLUDE_PATTERN.prop()), INCLUDE_PATTERN));
}
config.isValid();
} catch (ParseException e) {
log.error(e);
formatter.printHelp("java -jar MKVAudioSubtitlesChanger.jar -p <path_to_library>", options);
System.exit(1);
}
}
private static Options initOptions() {
Options options = new Options();
options.addOption(optionOf(HELP, "h", false));
options.addOption(optionOf(LIBRARY, "l", true, true));
options.addOption(optionOf(CONFIG_PATH, "c", false));
options.addOption(optionOf(THREADS, "t", true));
options.addOption(optionOf(SAFE_MODE, "s", false));
options.addOption(optionOf(FORCED_KEYWORDS, "k", Option.UNLIMITED_VALUES, false));
options.addOption(optionOf(EXCLUDE_DIRECTORY, "e", Option.UNLIMITED_VALUES, false));
options.addOption(optionOf(INCLUDE_PATTERN, "i", true));
return options;
}
}

View File

@@ -1,45 +1,57 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.MkvToolNix;
import at.pcgamingfreaks.yaml.YAML;
import at.pcgamingfreaks.yaml.YamlInvalidContentException;
import at.pcgamingfreaks.yaml.YamlKeyNotFoundException;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.cli.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty.*;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.CommandLineOptionsUtil.optionOf;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.LanguageValidatorUtil.isLanguageValid;
@Log4j2
@Getter
@Setter
public class Config {
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
CommandLineParser parser = new DefaultParser();
@Getter(AccessLevel.NONE)
HelpFormatter formatter = new HelpFormatter();
@Getter(AccessLevel.NONE)
private static Config config = null;
private List<AttributeConfig> attributeConfig;
private int threadCount;
private Set<String> forcedKeywords = new HashSet<>(Arrays.asList("forced", "signs"));
private Set<String> excludedDirectories = new HashSet<>();
private Pattern includePattern;
@Getter(AccessLevel.NONE)
private String mkvtoolnixPath;
private File configPath;
private String libraryPath;
private boolean isSafeMode;
private int threadCount;
private Pattern includePattern;
@Getter(AccessLevel.NONE)
private String mkvToolNixPath;
private boolean isWindows;
private final Set<String> forcedKeywords = new HashSet<>(Arrays.asList("forced", "signs"));
private final Set<String> excludedDirectories = new HashSet<>();
private List<AttributeConfig> attributeConfig;
public static Config getInstance() {
if (config == null) {
config = new Config();
@@ -47,99 +59,155 @@ public class Config {
return config;
}
public void isValid() throws RuntimeException {
boolean isValid = true;
if (attributeConfig == null || attributeConfig.isEmpty()
|| !attributeConfig.stream().allMatch(AttributeConfig::isValid)) {
isValid = false;
System.out.println("Audio & subtitle configuration invalid!");
}
if (threadCount <= 0) {
isValid = false;
System.out.println("Thread count needs to be at least 1!");
}
if (mkvtoolnixPath.isEmpty()
|| !new File(getPathFor(MkvToolNix.MKV_MERGER)).isFile()
|| !new File(getPathFor(MkvToolNix.MKV_PROP_EDIT)).isFile()) {
isValid = false;
System.out.println("MkvToolNix installation path invalid!");
public void initConfig(String[] args) throws InvalidConfigException {
ConfigErrors errors = new ConfigErrors();
CommandLine cmd = null;
Options options = initOptions();
try {
cmd = parser.parse(options, args);
if (cmd == null) throw new NullPointerException();
} catch (ParseException | NullPointerException e) {
formatter.printHelp(106, "java -jar MKVAudioSubtitlesChanger.jar -l <path_to_library>",
"\nParameters:", options,
"\nFeature requests and bug reports: https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/issues");
System.exit(1);
}
if (!isValid) {
throw new RuntimeException("Invalid configuration");
exitIfHelp(cmd, options);
configPath = loadConfigPath(cmd, errors);
libraryPath = loadLibraryPath(cmd, errors);
isSafeMode = cmd.hasOption(SAFE_MODE.prop());
try (YAML config = new YAML(configPath)) {
threadCount = loadThreadCount(cmd, config);
includePattern = loadIncludePattern(cmd, config, errors);
mkvToolNixPath = loadMkvToolNixPath(cmd, config, errors);
isWindows = loadOperatingSystem();
loadForcedKeywords(cmd, config);
loadExcludedDirectories(cmd, config);
attributeConfig = loadAttributeConfig(config, errors);
} catch (IOException | YamlInvalidContentException ignored) {}
if (errors.hasErrors()) {
throw new InvalidConfigException(errors);
}
}
public void loadConfig(String configPath) {
try (YAML config = new YAML(new File(configPath))) {
setAttributeConfig(loadAttributeConfig(config));
setThreadCount(loadThreadCount(config));
setMkvtoolnixPath(loadMkvToolNixPath(config));
setWindows(System.getProperty("os.name").toLowerCase().contains("windows"));
getForcedKeywords().addAll(loadForcedKeywords(config));
getExcludedDirectories().addAll(loadExcludeDirectories(config));
loadIncludePattern(config);
} catch (YamlInvalidContentException | YamlKeyNotFoundException | IOException e) {
log.fatal("Config could not be loaded: {}", e.getMessage());
private static Options initOptions() {
Options options = new Options();
options.addOption(optionOf(HELP, "h", false));
options.addOption(optionOf(LIBRARY, "l", true));
options.addOption(optionOf(MKV_TOOL_NIX, "m", true));
options.addOption(optionOf(CONFIG_PATH, "c", true));
options.addOption(optionOf(THREADS, "t", true));
options.addOption(optionOf(SAFE_MODE, "s", false));
options.addOption(optionOf(FORCED_KEYWORDS, "k", Option.UNLIMITED_VALUES, false));
options.addOption(optionOf(EXCLUDE_DIRECTORY, "e", Option.UNLIMITED_VALUES, false));
options.addOption(optionOf(INCLUDE_PATTERN, "i", true));
return options;
}
private void exitIfHelp(CommandLine cmd, Options options) {
if (cmd.hasOption("help")) {
formatter.printHelp(106, "java -jar MKVAudioSubtitlesChanger.jar -l <path_to_library>",
"\nParameters:", options,
"\nFeature requests and bug reports: https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/issues");
System.exit(0);
}
}
private List<AttributeConfig> loadAttributeConfig(YAML config) {
private File loadConfigPath(CommandLine cmd, ConfigErrors errors) {
File configPath = new File(cmd.getOptionValue(CONFIG_PATH.prop(), "config.yaml"));
if (configPath.isFile()) return configPath;
errors.add("invalid config path");
return null;
}
private String loadLibraryPath(CommandLine cmd, ConfigErrors errors) {
if (cmd.hasOption(LIBRARY.prop())) {
File libraryPath = new File(cmd.getOptionValue(LIBRARY.prop()));
if (libraryPath.isFile() || libraryPath.isDirectory()) {
return libraryPath.getAbsolutePath();
} else {
errors.add("invalid library path");
}
} else {
errors.add("missing library path");
}
return null;
}
private int loadThreadCount(CommandLine cmd, YAML config) {
return cmd.hasOption(THREADS.prop())
? Integer.parseInt(cmd.getOptionValue(THREADS.prop()))
: config.getInt(THREADS.prop(), 2);
}
private Pattern loadIncludePattern(CommandLine cmd, YAML config, ConfigErrors errors) {
try {
return Pattern.compile(cmd.hasOption(INCLUDE_PATTERN.prop())
? cmd.getOptionValue(INCLUDE_PATTERN.prop())
: config.getString(INCLUDE_PATTERN.prop(), ".*"));
} catch (PatternSyntaxException e) {
errors.add("invalid regex pattern");
}
return null;
}
@SneakyThrows
private String loadMkvToolNixPath(CommandLine cmd, YAML config, ConfigErrors errors){
if (cmd.hasOption(MKV_TOOL_NIX.prop())) return cmd.getOptionValue(MKV_TOOL_NIX.prop());
if (config.isSet(MKV_TOOL_NIX.prop())) return config.getString(MKV_TOOL_NIX.prop());
errors.add("path to mkv tool nix installation missing");
return null;
}
private boolean loadOperatingSystem() {
return System.getProperty("os.name").toLowerCase().contains("windows");
}
@SneakyThrows
private void loadForcedKeywords(CommandLine cmd, YAML config) {
if (cmd.hasOption(FORCED_KEYWORDS.prop())) forcedKeywords.addAll(List.of(cmd.getOptionValues(FORCED_KEYWORDS.prop())));
if (config.isSet(FORCED_KEYWORDS.prop())) forcedKeywords.addAll(config.getStringList(FORCED_KEYWORDS.prop()));
}
@SneakyThrows
private void loadExcludedDirectories(CommandLine cmd, YAML config) {
if (cmd.hasOption(EXCLUDE_DIRECTORY.prop())) excludedDirectories.addAll(List.of(cmd.getOptionValues(EXCLUDE_DIRECTORY.prop())));
if (config.isSet(EXCLUDE_DIRECTORY.prop())) excludedDirectories.addAll(config.getStringList(EXCLUDE_DIRECTORY.prop()));
}
private List<AttributeConfig> loadAttributeConfig(YAML config, ConfigErrors errors) {
Function<String, String> audio = key -> config.getString(key + ".audio", null);
Function<String, String> subtitle = key -> config.getString(key + ".subtitle", null);
return config.getKeysFiltered(".*audio.*").stream()
List<AttributeConfig> attributeConfigs = config.getKeysFiltered(".*audio.*").stream()
.sorted()
.map(key -> key.replace(".audio", ""))
.map(key -> new AttributeConfig(audio.apply(key), subtitle.apply(key)))
.collect(Collectors.toList());
}
private int loadThreadCount(YAML config) throws YamlKeyNotFoundException {
return config.isSet(ConfigProperty.THREADS.prop())
? Integer.parseInt(config.getString(ConfigProperty.THREADS.prop()))
: 1;
}
private List<String> loadForcedKeywords(YAML config) {
return config.getStringList(ConfigProperty.FORCED_KEYWORDS.prop(), new ArrayList<>());
}
private List<String> loadExcludeDirectories(YAML config) {
return config.getStringList(ConfigProperty.EXCLUDE_DIRECTORY.prop(), new ArrayList<>());
}
private void loadIncludePattern(YAML config) throws YamlKeyNotFoundException {
if (config.isSet(INCLUDE_PATTERN.prop())) {
includePattern = compilePattern(config.getString(INCLUDE_PATTERN.prop()), INCLUDE_PATTERN);
if (attributeConfigs.isEmpty()) {
errors.add("no language configuration");
} else {
includePattern = Pattern.compile(".*");
for (AttributeConfig attributeConfig : attributeConfigs) {
isLanguageValid(attributeConfig.getAudioLanguage(), errors);
isLanguageValid(attributeConfig.getSubtitleLanguage(), errors);
}
}
}
public static Pattern compilePattern(String pattern, ConfigProperty origin) {
try {
return Pattern.compile(pattern);
} catch (PatternSyntaxException e) {
log.error("{} is not a valid regex pattern!", origin.prop());
throw new RuntimeException(String.format("%s is not a valid regex pattern!%n", origin.prop()));
}
}
private String loadMkvToolNixPath(YAML config) throws YamlKeyNotFoundException {
return config.isSet(MKV_TOOL_NIX.prop())
? config.getString(MKV_TOOL_NIX.prop())
: defaultMkvToolNixPath();
}
private String defaultMkvToolNixPath() {
return System.getProperty("os.name").toLowerCase().contains("windows")
? "C:/Program Files/MKVToolNix/"
: "/usr/bin/";
return attributeConfigs;
}
public String getPathFor(MkvToolNix exe) {
return mkvtoolnixPath.endsWith("/") ? mkvtoolnixPath + exe : mkvtoolnixPath + "/" + exe;
return mkvToolNixPath.endsWith("/") ? mkvToolNixPath + exe : mkvToolNixPath + "/" + exe;
}
}

View File

@@ -0,0 +1,22 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
public class ConfigErrors {
private final List<String> errors = new ArrayList<>();
public void add(String errorMessage) {
errors.add(errorMessage);
}
public boolean hasErrors() {
return !errors.isEmpty();
}
public String toString() {
return StringUtils.capitalize(String.join(", ", errors));
}
}

View File

@@ -0,0 +1,7 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
public class InvalidConfigException extends RuntimeException{
public InvalidConfigException(ConfigErrors errors) {
super("Errors in config: " + errors);
}
}

View File

@@ -14,10 +14,6 @@ public class AttributeConfig {
this.subtitleLanguage = subtitleLanguage;
}
public boolean isValid() {
return audioLanguage != null && subtitleLanguage != null;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("AttributeConfig{");

View File

@@ -1,15 +1,15 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
public enum ConfigProperty {
MKV_TOOL_NIX("mkvtoolnixPath", "Path to mkv tool nix installation"),
THREADS("threads", "thread count"),
FORCED_KEYWORDS("forcedKeywords", "Additional keywords to identify forced tracks\""),
CONFIG_PATH("config", "path to config"),
LIBRARY("library", "path to library"),
CONFIG_PATH("config", "Path to config file"),
LIBRARY("library", "Path to library"),
SAFE_MODE("safe-mode", "Test run (no files will be changes)"),
HELP("help", "\"for help this is\" - Yoda"),
EXCLUDE_DIRECTORY("exclude-directories", "Directories to exclude"),
INCLUDE_PATTERN("include-pattern", "Include files matching pattern");
THREADS("threads", "thread count (default: 2)"),
INCLUDE_PATTERN("include-pattern", "Include files matching pattern"),
MKV_TOOL_NIX("mkvtoolnix", "Path to mkv tool nix installation"),
FORCED_KEYWORDS("forcedKeywords", "Additional keywords to identify forced tracks, combines with config file"),
EXCLUDE_DIRECTORY("exclude-directories", "Directories to be excluded, combines with config file"),
HELP("help", "\"for help this is\" - Yoda");
private final String property;
private final String description;

View File

@@ -0,0 +1,44 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ConfigErrors;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
public class LanguageValidatorUtil {
private static Set<String> ISO3_LANGUAGES;
static {
try {
ISO3_LANGUAGES = load("language-codes");
} catch (IOException ignored) {}
}
private static Set<String> load(String path) throws IOException{
if (new File(path).isFile()) {
return Files.lines(Path.of(path)).collect(Collectors.toSet());
} else {
try(BufferedReader bf = new BufferedReader(new InputStreamReader(
Objects.requireNonNull(LanguageValidatorUtil.class.getClassLoader().getResourceAsStream(path))))) {
return bf.lines().collect(Collectors.toSet());
}
}
}
public static boolean isLanguageValid(String language) {
return ISO3_LANGUAGES.contains(language);
}
public static void isLanguageValid(String language, ConfigErrors errors) {
if (!isLanguageValid(language)) {
errors.add(String.format("%s is not a valid language", language));
}
}
}