24 Commits

Author SHA1 Message Date
5d86b43388 Update README.md 2022-04-02 18:36:13 +02:00
d637558cfe Merge remote-tracking branch 'origin/master' 2022-04-02 18:14:18 +02:00
4e53d99c25 Add progress bar 2022-04-02 18:13:46 +02:00
Michael
a19fbac03a Merge pull request #18 from RatzzFatzz/dependabot/maven/com.fasterxml.jackson.core-jackson-databind-2.13.2.1
Bump jackson-databind from 2.13.1 to 2.13.2.1
2022-03-30 22:49:31 +02:00
a5b0224d6c Reimplement basic statistics 2022-03-30 20:32:26 +02:00
f7a2e4234a Add cli property for forced keywords 2022-03-30 20:09:32 +02:00
93f3542cf1 Add new file update process 2022-03-30 19:00:24 +02:00
dependabot[bot]
b94f6eb7de Bump jackson-databind from 2.13.1 to 2.13.2.1
Bumps [jackson-databind](https://github.com/FasterXML/jackson) from 2.13.1 to 2.13.2.1.
- [Release notes](https://github.com/FasterXML/jackson/releases)
- [Commits](https://github.com/FasterXML/jackson/commits)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.core:jackson-databind
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-28 20:35:33 +00:00
c11431d85b Change track selection 2022-03-28 22:35:02 +02:00
68e5b9e988 Change AttributeConfig to only hold one audio & subtitle lane 2022-03-24 19:07:58 +01:00
a1e9031cbc Restructure files & remove unused code & simplify few statements 2022-03-23 21:02:28 +01:00
1df06e8a08 Add safe-mode 2022-03-21 23:21:15 +01:00
feaeda746a Implement cli parameter basis 2022-03-20 20:49:34 +01:00
f2fb296698 Make use of String.format 2022-03-17 18:58:05 +01:00
03efab657c Implement multi threading to improve throughput 2022-03-06 20:02:10 +01:00
bba612476b Update dependencies & fix null pointer exception when file has missing properties or none at all 2022-02-23 00:16:14 +01:00
Michael
097499a916 Merge pull request #8 from RatzzFatzz/dependabot/maven/org.apache.logging.log4j-log4j-api-2.15.0
Bump log4j-api from 2.12.0 to 2.15.0
2021-12-12 14:45:27 +01:00
Michael
3617d84b3e Merge pull request #9 from RatzzFatzz/dependabot/maven/org.apache.logging.log4j-log4j-core-2.15.0
Bump log4j-core from 2.13.2 to 2.15.0
2021-12-12 14:45:12 +01:00
dependabot[bot]
ecbfc77da0 Bump log4j-core from 2.13.2 to 2.15.0
Bumps log4j-core from 2.13.2 to 2.15.0.

---
updated-dependencies:
- dependency-name: org.apache.logging.log4j:log4j-core
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-10 01:26:51 +00:00
dependabot[bot]
1ed1b6e057 Bump log4j-api from 2.12.0 to 2.15.0
Bumps log4j-api from 2.12.0 to 2.15.0.

---
updated-dependencies:
- dependency-name: org.apache.logging.log4j:log4j-api
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-10 00:59:52 +00:00
Michael
013c46d940 Merge pull request #7 from RatzzFatzz/dependabot/maven/org.apache.logging.log4j-log4j-core-2.13.2
Bump log4j-core from 2.12.0 to 2.13.2
2020-09-08 09:38:56 +02:00
dependabot[bot]
a41895df1b Bump log4j-core from 2.12.0 to 2.13.2
Bumps log4j-core from 2.12.0 to 2.13.2.

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-01 19:23:34 +00:00
Michael
6ba5064ca9 Merge pull request #6 from RatzzFatzz/dependabot/maven/com.fasterxml.jackson.core-jackson-databind-2.10.0.pr1
Bump jackson-databind from 2.9.10.4 to 2.10.0.pr1
2020-06-24 20:12:44 +02:00
dependabot[bot]
9775be153b Bump jackson-databind from 2.9.10.4 to 2.10.0.pr1
Bumps [jackson-databind](https://github.com/FasterXML/jackson) from 2.9.10.4 to 2.10.0.pr1.
- [Release notes](https://github.com/FasterXML/jackson/releases)
- [Commits](https://github.com/FasterXML/jackson/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-18 16:58:21 +00:00
25 changed files with 686 additions and 502 deletions

View File

@@ -17,30 +17,33 @@ This program helps changing audio and subtitle lines of mkv files.
Opening terminal / cmd in the directory of the jar and the config file and execute following command:
`java -jar mkvaudiosubtitleschanger.jar [path to mkv or dir with mkv]`
`java -jar mkvaudiosubtitleschanger.jar -l [path to mkv or dir with mkv]`
You have to replace the brackets and the content of it with the path to your mkv file or the directory with mkv files.
### Additional arameters
```properties
-c,--config path to config
-h,--help "for help this is" - Yoda
-k,--forcedKeywords <arg> Additional keywords to identify forced tracks
-l,--library <arg> path to library (Required)
-s,--safe-mode Test run (no files will be changes)
-t,--threads <arg> thread count
```
### config.yml example
Config file needs to be placed in the same directory as the jar.
Config file needs to be placed in the same directory as the jar or path to config has to be passed via command line
argument.
```
mkvtoolnixPath: /usr/bin
```yaml
mkvtoolnixPath: C:\Program Files\MKVToolNix
# Recommendations for data stored on HDDs, increase when using SSDs
threads: 2
#forcedKeywords: ["forced", "signs"]
config:
1:
audio:
- jpn
subtitle:
- ger
- eng
audio: ger
subtitle: OFF
2:
audio:
- ger
- eng
subtitle:
- OFF
audio: eng
subtitle: ger
```
This config will first check if there is japanese audio and german or english subtitles available, if yes,
it will set these attributes. If these are not available, it will check the second part. This means, it checks
if german or english audio is available. It does not care for the subtitle, because it's "off", which means, it
will disable subtitles in this case.
Subtitle lanes recognized as forced will be set as one. Already existing ones will not be overwritten or changed.

11
config-template.yaml Normal file
View File

@@ -0,0 +1,11 @@
mkvtoolnixPath: C:\Program Files\MKVToolNix
# Recommendations for data stored on HDDs, increase when using SSDs
threads: 2
#forcedKeywords: ["forced", "signs"]
config:
1:
audio: ger
subtitle: OFF
2:
audio: eng
subtitle: ger

View File

@@ -1,14 +0,0 @@
mkvtoolnixPath: /usr/bin
config:
1:
audio:
- jpn
subtitle:
- ger
- eng
2:
audio:
- ger
- eng
subtitle:
- OFF

31
pom.xml
View File

@@ -4,9 +4,9 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>MKVAudioSubtilesChanger</groupId>
<artifactId>MKVAudioSubtitlesChanger</artifactId>
<version>1.0</version>
<groupId>MKVAudioSubtileChanger</groupId>
<artifactId>MKVAudioSubtitleChanger</artifactId>
<version>2.0</version>
<build>
<defaultGoal>clean package</defaultGoal>
@@ -106,17 +106,17 @@
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.12.0</version>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.12.0</version>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>2.12.0</version>
<version>2.17.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
@@ -137,12 +137,27 @@
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.9.9</version>
<version>2.13.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.10.4</version>
<version>2.13.2.1</version>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>me.tongfei</groupId>
<artifactId>progressbar</artifactId>
<version>0.9.3</version>
</dependency>
<!-- endregion -->
<!-- region unit-tests -->

View File

@@ -1,42 +1,78 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.AttributeConfig;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ConfigProcessor;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.intimpl.MkvFileCollector;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.FileCollector;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.FileProcessor;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.ConfigUtil;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfoDto;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ResultStatistic;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import me.tongfei.progressbar.ProgressBar;
import me.tongfei.progressbar.ProgressBarBuilder;
import me.tongfei.progressbar.ProgressBarStyle;
import java.io.File;
import java.util.ArrayList;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Log4j2
public class AttributeUpdaterKernel {
MkvFileCollector collector = new MkvFileCollector();
public void execute(String path) {
List<AttributeConfig> configPattern = ConfigUtil.loadConfig();
List<File> allValidPaths = collector.loadFiles(path);
if(allValidPaths != null && configPattern != null){
for(File file : allValidPaths){
List<FileAttribute> attributes = collector.loadAttributes(file);
boolean fileHasChanged = false;
for(AttributeConfig config : configPattern){
/*
* Creating new ArrayList, because the method removes elements from the list by reference
*/
fileHasChanged = new ConfigProcessor(config).processConfig(file, new ArrayList<>(attributes));
if(fileHasChanged){
break;
}
}
if(! fileHasChanged){
log.info(file.getName() + " didn't change!");
private final ExecutorService executor = Executors.newFixedThreadPool(Config.getInstance().getThreadCount());
private final FileCollector collector;
private final FileProcessor processor;
private final ResultStatistic statistic = new ResultStatistic();
public AttributeUpdaterKernel(FileCollector collector, FileProcessor processor) {
this.collector = collector;
this.processor = processor;
}
@SneakyThrows
public void execute() {
statistic.startTimer();
try (ProgressBar progressBar = pbBuilder().build()) {
List<File> files = collector.loadFiles(Config.getInstance().getLibraryPath());
progressBar.maxHint(files.size());
files.forEach(file -> executor.submit(() -> process(file, progressBar)));
executor.shutdown();
executor.awaitTermination(1, TimeUnit.DAYS);
}
statistic.stopTimer();
System.out.println(statistic);
}
private void process(File file, ProgressBar progressBar) {
statistic.total();
List<FileAttribute> attributes = processor.loadAttributes(file);
FileInfoDto fileInfo = processor.filterAttributes(attributes);
if (fileInfo.isChangeNecessary()) {
statistic.shouldChange();
if (!Config.getInstance().isSafeMode()) {
try {
processor.update(file, fileInfo);
statistic.success();
} catch (IOException e) {
statistic.failure();
log.warn("File couldn't be updated: {}", file.getAbsoluteFile());
}
}
}else{
log.error("Path is not valid or config has errors!");
} else {
statistic.fits();
}
progressBar.step();
}
private static ProgressBarBuilder pbBuilder() {
return new ProgressBarBuilder()
.setStyle(ProgressBarStyle.ASCII)
.setUpdateIntervalMillis(250)
.setMaxRenderedLength(75);
}
}

View File

@@ -17,7 +17,7 @@ public class MKVToolProperties {
}
public static MKVToolProperties getInstance() {
if(instance == null){
if (instance == null) {
instance = new MKVToolProperties();
}
return instance;

View File

@@ -1,40 +1,62 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
import at.pcgamingfreaks.yaml.YAML;
import at.pcgamingfreaks.yaml.YamlInvalidContentException;
import at.pcgamingfreaks.yaml.YamlKeyNotFoundException;
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.io.File;
import java.io.IOException;
import java.util.List;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty.*;
import static java.lang.Integer.parseInt;
@Log4j2
public class Main {
public static void main(String[] args) {
if(checkIfMKVToolNixIsValid()){
AttributeUpdaterKernel kernel = new AttributeUpdaterKernel();
kernel.execute(args[0]);
}else{
log.error("MKVToolNix was not found! Please recheck path");
initConfig(args);
AttributeUpdaterKernel kernel = new AttributeUpdaterKernel(new MkvFileCollector(), new MkvFileProcessor());
kernel.execute();
}
private static void initConfig(String[] args) {
Options options = new Options();
options.addOption("h", HELP.toString(), false, "\"for help this is\" - Yoda");
options.addRequiredOption("l", LIBRARY.toString(), true, "path to library");
options.addOption("c", CONFIG.toString(), false, "path to config");
options.addOption("t", THREADS.toString(), true, "thread count");
options.addOption("s", SAFE_MODE.toString(), false, "Test run (no files will be changes)");
options.addOption(create("k", FORCED_KEYWORDS.toString(), Option.UNLIMITED_VALUES, "Additional keywords to identify forced tracks"));
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.toString(), "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.toString()))
config.getForcedKeywords().addAll(List.of(cmd.getOptionValues(FORCED_KEYWORDS.toString())));
config.isValid();
} catch (ParseException e) {
log.error(e);
formatter.printHelp("java -jar MKVAudioSubtitlesChanger.jar -p <path_to_library>", options);
System.exit(1);
}
}
private static boolean checkIfMKVToolNixIsValid() {
try{
String path = new YAML(new File("config.yaml")).getString("mkvtoolnixPath");
if(! path.endsWith(File.separator)){
path += File.separator;
}
if(System.getProperty("os.name").toLowerCase().contains("windows")){
MKVToolProperties.getInstance().setMkvmergePath(path + "mkvmerge.exe");
MKVToolProperties.getInstance().setMkvpropeditPath(path + "mkvpropedit.exe");
}else{
MKVToolProperties.getInstance().setMkvmergePath(path + "mkvmerge");
MKVToolProperties.getInstance().setMkvpropeditPath(path + "mkvpropedit");
}
}catch(YamlKeyNotFoundException | IOException | YamlInvalidContentException e){
e.printStackTrace();
}
return new File(MKVToolProperties.getInstance().getMkvmergePath()).isFile() && new File(MKVToolProperties.getInstance().getMkvpropeditPath()).isFile();
private static Option create(String opt, String longOpt, int args, String desc) {
Option option = new Option(opt, desc);
option.setLongOpt(longOpt);
option.setArgs(args);
return option;
}
}

View File

@@ -1,18 +0,0 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import java.util.List;
@Log4j2
@Getter
public class AttributeConfig {
private List<String> audio;
private List<String> subtitle;
public AttributeConfig(List<String> audio, List<String> subtitle) {
this.audio = audio;
this.subtitle = subtitle;
}
}

View File

@@ -0,0 +1,118 @@
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.extern.log4j.Log4j2;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty.MKV_TOOL_NIX;
@Log4j2
@Getter
@Setter
public class Config {
@Getter(AccessLevel.NONE)
@Setter(AccessLevel.NONE)
private static Config config = null;
private List<AttributeConfig> attributeConfig;
private int threadCount;
private Set<String> forcedKeywords = new HashSet<>(Arrays.asList("forced", "signs"));
@Getter(AccessLevel.NONE)
private String mkvtoolnixPath;
private String libraryPath;
private boolean isSafeMode;
private boolean isWindows;
public static Config getInstance() {
if (config == null) {
config = new 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!");
}
if (!isValid) {
throw new RuntimeException("Invalid configuration");
}
}
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));
} catch (YamlInvalidContentException | YamlKeyNotFoundException | IOException e) {
log.fatal("Config could not be loaded: {}", e.getMessage());
}
}
private List<AttributeConfig> loadAttributeConfig(YAML config) {
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()
.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.toString())
? Integer.parseInt(config.getString(ConfigProperty.THREADS.toString()))
: 1;
}
private List<String> loadForcedKeywords(YAML config) {
return config.getStringList(ConfigProperty.FORCED_KEYWORDS.toString(), new ArrayList<>());
}
private String loadMkvToolNixPath(YAML config) throws YamlKeyNotFoundException {
return config.isSet(MKV_TOOL_NIX.toString())
? config.getString(MKV_TOOL_NIX.toString())
: defaultMkvToolNixPath();
}
private String defaultMkvToolNixPath() {
return System.getProperty("os.name").toLowerCase().contains("windows")
? "C:/Program Files/MKVToolNix/"
: "/usr/bin/";
}
public String getPathFor(MkvToolNix exe) {
return mkvtoolnixPath.endsWith("/") ? mkvtoolnixPath + exe : mkvtoolnixPath + "/" + exe;
}
}

View File

@@ -1,194 +0,0 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.MKVToolProperties;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
@Log4j2
public class ConfigProcessor {
private int audioDefault = - 1;
private int subtitleDefault = - 1;
private final AttributeConfig config;
public ConfigProcessor(AttributeConfig config) {
this.config = config;
}
/**
* Processes the config lists and apply the changes if the combination matches
*
* @param file is the file, which will be updated
* @param attributes has the metadata for the transferred file
* @return If the current configuration matched and changes applied or not
*/
public boolean processConfig(File file, List<FileAttribute> attributes) {
// check if size is bigger or equal 2 to make sure that there is at least one audio and subtitle line
// TODO: implement empty audio or subtitle line
if(attributes.size() >= 2){
TransferObject transfer = filterAttributes(attributes);
if(! attributes.isEmpty()){
return updateFile(file, attributes, transfer);
}
}
return false;
}
/*
* Filters the attributes that only those are remaining which are needed in the current configuration.
* Also analyzes which tracks were default before.
*/
private TransferObject filterAttributes(List<FileAttribute> attributes) {
TransferObject transfer = new TransferObject();
Iterator<FileAttribute> iterator = attributes.iterator();
while(iterator.hasNext()){
FileAttribute elem = iterator.next();
if("audio".equals(elem.getType())){
if(elem.isDefaultTrack()){
audioDefault = elem.getId();
}
if(config.getAudio().contains("OFF")){
transfer.setAudioOn(false);
transfer.setAudioIndex(- 2);
}
if(! config.getAudio().contains(elem.getLanguage())){
iterator.remove();
}
}else if("subtitles".equals(elem.getType())){
if(elem.isDefaultTrack()){
subtitleDefault = elem.getId();
}
if(config.getSubtitle().contains("OFF")){
transfer.setSubtitleOn(false);
transfer.setSubtitleIndex(- 2);
}
if(! config.getSubtitle().contains(elem.getLanguage())){
iterator.remove();
}
}
}
return transfer;
}
/**
* Creates the command which will be executed if the attributes of the current file fit the current {@link AttributeConfig}
*
* @param file is the file, which will be updated
* @param attributes has the metadata for the transferred file
* @return if the the current file was updated or not. Returns true if the file already has the correct metadata set
*/
private boolean updateFile(File file, List<FileAttribute> attributes, TransferObject transfer) {
StringBuilder stringBuffer = new StringBuilder();
if(System.getProperty("os.name").toLowerCase().contains("windows")){
stringBuffer.append("\"");
stringBuffer.append(MKVToolProperties.getInstance().getMkvpropeditPath());
stringBuffer.append("\" \"").append(file.getAbsolutePath()).append("\" ");
}else{
stringBuffer.append(MKVToolProperties.getInstance().getMkvpropeditPath());
stringBuffer.append(" ").append(file.getAbsolutePath()).append(" ");
}
if(audioDefault != - 1){
stringBuffer.append("--edit track:=").append(audioDefault).append(" --set flag-default=0 ");
}
if(subtitleDefault != - 1){
stringBuffer.append("--edit track:=").append(subtitleDefault).append(" --set flag-default=0 ");
}
collectLines(attributes, transfer);
if(transfer.isValid){
if(transfer.isAudioOn){
stringBuffer.append("--edit track:=").append(transfer.getAudioIndex()).append(" --set flag-default=1 ");
}
if(transfer.isSubtitleOn){
stringBuffer.append("--edit track:=").append(transfer.getSubtitleIndex()).append(" --set flag-default=1 ");
}
if(subtitleDefault == transfer.getSubtitleIndex() && audioDefault == transfer.getAudioIndex()){
/*
* In this case the file would be change to the exact same audio and subtitle lines and we want to
* avoid unnecessary changes to the file
*/
log.info(file.getName() + " already fits config!");
return true;
}
try{
Runtime.getRuntime().exec(stringBuffer.toString());
}catch(IOException e){
log.error("Couldn't make changes to file");
}
/*
* We return true even if there was an error. If there was an error, the chances that this file is still
* busy later.
*/
log.info(file.getName() + " was updated");
return true;
}else{
return false;
}
}
/**
* Analyzes the left over attributes and decides which is the most wanted for audio and subtitle
*
* @param attributes contains all the leftover attributes
* @return is an object, which contains information about which audio and subtitle line is the best suitable for
* the entered config. Also transfers a boolean which contains information about if the other two values
* were set
*/
private TransferObject collectLines(List<FileAttribute> attributes, TransferObject transfer) {
int subtitleListIndex = - 1;
int audioListIndex = - 1;
for(FileAttribute elem : attributes){
if("audio".equals(elem.getType())){
for(int i = 0; i < config.getAudio().size(); i++){
audioListIndex = findIndex("audio", elem, audioListIndex, config.getAudio(), transfer);
}
}else if("subtitles".equals(elem.getType())){
for(int i = 0; i < config.getSubtitle().size(); i++){
subtitleListIndex = findIndex("subtitles", elem, subtitleListIndex, config.getSubtitle(), transfer);
}
}
}
transfer.analyzeIfValid();
return transfer;
}
private int findIndex(String type, FileAttribute elem, int index, List<String> config, TransferObject transfer) {
for(int i = 0; i < config.size(); i++){
if(config.get(i).equals(elem.getLanguage()) && (index == - 1 || i < index)){
switch(type){
case "audio":
transfer.setAudioIndex(elem.getId());
break;
case "subtitles":
transfer.setSubtitleIndex(elem.getId());
break;
}
return i;
}
}
return index;
}
@Getter
@Setter
private static class TransferObject {
private boolean isValid;
private int audioIndex = - 1;
private int subtitleIndex = - 1;
private boolean isSubtitleOn = true;
private boolean isAudioOn = true;
TransferObject() {
}
private void analyzeIfValid() {
isValid = audioIndex != - 1 && subtitleIndex != - 1;
}
}
}

View File

@@ -0,0 +1,13 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
import java.io.File;
import java.util.List;
public interface FileCollector {
/**
* @param path leads to one file directly or a directory which will be loaded recursively
* @return list of all files within the directory
*/
List<File> loadFiles(String path);
}

View File

@@ -0,0 +1,21 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfoDto;
import java.io.File;
import java.io.IOException;
import java.util.List;
public interface FileProcessor {
/**
* @param file Takes the file from which the attributes will be returned
* @return list of all important attributes
*/
List<FileAttribute> loadAttributes(File file);
FileInfoDto filterAttributes(List<FileAttribute> attributes);
void update(File file, FileInfoDto fileInfo) throws IOException;
}

View File

@@ -0,0 +1,40 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
import lombok.extern.log4j.Log4j2;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Log4j2
public class MkvFileCollector implements FileCollector {
@Override
public List<File> loadFiles(String path) {
File file = new File(path);
if (file.isFile() && file.getAbsolutePath().endsWith(".mkv")) {
return new ArrayList<File>() {{
add(file);
}};
} else if (file.isDirectory()) {
try (Stream<Path> paths = Files.walk(Paths.get(path))) {
return paths
.filter(Files::isRegularFile)
.map(Path::toFile)
.filter(f -> f.getAbsolutePath().endsWith(".mkv"))
.collect(Collectors.toList());
} catch (IOException e) {
log.error("Couldn't find file or directory!", e);
return new ArrayList<>();
}
} else {
return new ArrayList<>();
}
}
}

View File

@@ -0,0 +1,144 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.core.util.IOUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.stream.Collectors;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.LaneType.AUDIO;
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.LaneType.SUBTITLES;
import static java.lang.String.format;
@Log4j2
public class MkvFileProcessor implements FileProcessor {
private final ObjectMapper mapper = new ObjectMapper();
private final String[] forcedKeywords = new String[]{"forced", "signs"};
private static final String DISABLE_DEFAULT_TRACK = "--edit track:%s --set flag-default=0 ";
private static final String ENABLE_DEFAULT_TRACK = "--edit track:%s --set flag-default=1 ";
private static final String ENABLE_FORCED_TRACK = "--edit track:%s --set flag-forced=1 ";
@Override
public List<FileAttribute> loadAttributes(File file) {
Map<String, Object> jsonMap;
List<FileAttribute> fileAttributes = new ArrayList<>();
try {
String command = format("\"%s\"", Config.getInstance().getPathFor(MkvToolNix.MKV_MERGER));
String[] arguments = new String[]{
command,
"--identify",
"--identification-format",
"json",
file.getAbsoluteFile().toString()
};
InputStream inputStream = Runtime.getRuntime().exec(arguments).getInputStream();
jsonMap = mapper.readValue(inputStream, Map.class);
List<Map<String, Object>> tracks = (List<Map<String, Object>>) jsonMap.get("tracks");
if (tracks == null) {
log.warn("Couldn't retrieve information of {}", file.getAbsolutePath());
return new ArrayList<>();
}
for (Map<String, Object> attribute : tracks) {
if (!"video".equals(attribute.get("type"))) {
Map<String, Object> properties = (Map<String, Object>) attribute.get("properties");
fileAttributes.add(new FileAttribute(
(int) properties.get("number"),
(String) properties.get("language"),
(String) properties.get("track_name"),
(Boolean) properties.getOrDefault("default_track", false),
(Boolean) properties.getOrDefault("forced_track", false),
LaneType.valueOf(((String) attribute.get("type")).toUpperCase(Locale.ENGLISH))));
}
}
} catch (IOException e) {
e.printStackTrace();
log.error("File could not be found or loaded!");
}
return fileAttributes;
}
@Override
public FileInfoDto filterAttributes(List<FileAttribute> attributes) {
FileInfoDto info = new FileInfoDto();
List<FileAttribute> nonForcedTracks = attributes.stream()
.filter(elem -> !StringUtils.containsAnyIgnoreCase(elem.getTrackName(), forcedKeywords))
.collect(Collectors.toList());
detectCurrentConfiguration(attributes, info, nonForcedTracks);
detectDesiredConfiguration(info, nonForcedTracks);
return info;
}
private void detectCurrentConfiguration(List<FileAttribute> attributes, FileInfoDto info, List<FileAttribute> nonForcedTracks) {
Set<FileAttribute> detectedForcedSubtitleLanes = new HashSet<>();
for (FileAttribute attribute : attributes) {
if (attribute.isDefaultTrack() && AUDIO.equals(attribute.getType())) info.setDefaultAudioLane(attribute);
if (attribute.isDefaultTrack() && SUBTITLES.equals(attribute.getType()))
info.setDefaultSubtitleLane(attribute);
if (attribute.isForcedTrack() && SUBTITLES.equals(attribute.getType()))
detectedForcedSubtitleLanes.add(attribute);
}
info.setDesiredForcedSubtitleLanes(attributes.stream()
.filter(e -> !nonForcedTracks.contains(e))
.filter(e -> !detectedForcedSubtitleLanes.contains(e))
.collect(Collectors.toSet())
);
}
private void detectDesiredConfiguration(FileInfoDto info, List<FileAttribute> nonForcedTracks) {
for (AttributeConfig config : Config.getInstance().getAttributeConfig()) {
FileAttribute desiredAudio = null;
FileAttribute desiredSubtitle = null;
for (FileAttribute attribute : nonForcedTracks) {
if (attribute.getLanguage().equals(config.getAudioLanguage())
&& AUDIO.equals(attribute.getType())) desiredAudio = attribute;
if (attribute.getLanguage().equals(config.getSubtitleLanguage())
&& SUBTITLES.equals(attribute.getType())) desiredSubtitle = attribute;
}
if (desiredAudio != null && desiredSubtitle != null) {
info.setDesiredAudioLane(desiredAudio);
info.setDesiredSubtitleLane(desiredSubtitle);
break;
}
}
}
@Override
public void update(File file, FileInfoDto fileInfo) throws IOException {
StringBuffer sb = new StringBuffer();
sb.append(format("\"%s\" ", Config.getInstance().getPathFor(MkvToolNix.MKV_PROP_EDIT)));
sb.append(format("\"%s\" ", file.getAbsolutePath()));
if (fileInfo.isAudioDifferent()) {
if (fileInfo.getDefaultAudioLane() != null) {
sb.append(format(DISABLE_DEFAULT_TRACK, fileInfo.getDefaultAudioLane().getId()));
}
sb.append(format(ENABLE_DEFAULT_TRACK, fileInfo.getDesiredAudioLane().getId()));
}
if (fileInfo.isSubtitleDifferent()) {
if (fileInfo.getDefaultSubtitleLane() != null) {
sb.append(format(DISABLE_DEFAULT_TRACK, fileInfo.getDefaultSubtitleLane().getId()));
}
sb.append(format(ENABLE_DEFAULT_TRACK, fileInfo.getDesiredSubtitleLane().getId()));
}
if (fileInfo.areForcedTracksDifferent()) {
for (FileAttribute attribute : fileInfo.getDesiredForcedSubtitleLanes()) {
sb.append(format(ENABLE_FORCED_TRACK, attribute.getId()));
}
}
InputStream inputstream = Runtime.getRuntime().exec(sb.toString()).getInputStream();
log.debug(IOUtils.toString(new InputStreamReader(inputstream)));
}
}

View File

@@ -1,12 +0,0 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.intimpl;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
import java.io.File;
import java.util.List;
public interface FileCollector {
List<File> loadFiles(String path);
List<FileAttribute> loadAttributes(File file);
}

View File

@@ -1,95 +0,0 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.intimpl;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.MKVToolProperties;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.log4j.Log4j2;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Log4j2
public class MkvFileCollector implements FileCollector {
private final ObjectMapper mapper = new ObjectMapper();
/**
* @param path Is entered path, which leads to one file directly or a directory which will be loaded recursive
* @return list of all files within the directory, if it's only a file, the file will be returned in a list
*/
@Override
public List<File> loadFiles(String path) {
File file = new File(path);
if(file.isFile() && file.getAbsolutePath().endsWith(".mkv")){
return new ArrayList<File>() {{
add(file);
}};
}else if(file.isDirectory()){
try(Stream<Path> paths = Files.walk(Paths.get(path))){
return paths
.filter(Files::isRegularFile)
.map(Path::toFile)
.filter(f -> f.getAbsolutePath().endsWith(".mkv"))
.collect(Collectors.toList());
}catch(IOException e){
log.error("Couldn't find file or directory!", e);
return null;
}
}else{
return null;
}
}
/**
* @param file Takes the file from which the attributes will be returned
* @return list of all important attributes
*/
@Override
public List<FileAttribute> loadAttributes(File file) {
Map<String, Object> jsonMap;
List<FileAttribute> fileAttributes = new ArrayList<>();
try{
String command = "";
if(System.getProperty("os.name").toLowerCase().contains("windows")){
command = "\"" + MKVToolProperties.getInstance().getMkvmergePath() + "\"";
}else{
command = MKVToolProperties.getInstance().getMkvmergePath();
}
String[] array = new String[]{
command,
"--identify",
"--identification-format",
"json",
file.getAbsoluteFile().toString()
};
InputStream inputStream = Runtime.getRuntime().exec(array).getInputStream();
jsonMap = mapper.readValue(inputStream, Map.class);
List<Map<String, Object>> tracks = (List<Map<String, Object>>) jsonMap.get("tracks");
for(Map<String, Object> attribute : tracks){
if(! "video".equals(attribute.get("type"))){
Map<String, Object> properties = (Map<String, Object>) attribute.get("properties");
fileAttributes.add(new FileAttribute(
(int) properties.get("number"),
(String) properties.get("language"),
(String) properties.get("track_name"),
(Boolean) properties.get("default_track"),
(Boolean) properties.get("forced_track"),
(String) attribute.get("type")));
}
}
}catch(IOException e){
e.printStackTrace();
log.error("File could not be found or loaded!");
}
return fileAttributes;
}
}

View File

@@ -0,0 +1,29 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;
@Log4j2
@Getter
public class AttributeConfig {
private final String audioLanguage;
private final String subtitleLanguage;
public AttributeConfig(String audioLanguage, String subtitleLanguage) {
this.audioLanguage = audioLanguage;
this.subtitleLanguage = subtitleLanguage;
}
public boolean isValid() {
return audioLanguage != null && subtitleLanguage != null;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("AttributeConfig{");
sb.append("audioLanguage='").append(audioLanguage).append('\'');
sb.append(", subtitleLanguage='").append(subtitleLanguage).append('\'');
sb.append('}');
return sb.toString();
}
}

View File

@@ -0,0 +1,22 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
public enum ConfigProperty {
MKV_TOOL_NIX("mkvtoolnixPath"),
THREADS("threads"),
FORCED_KEYWORDS("forcedKeywords"),
CONFIG("config"),
LIBRARY("library"),
SAFE_MODE("safe-mode"),
HELP("help");
private final String property;
ConfigProperty(String property) {
this.property = property;
}
@Override
public String toString() {
return property;
}
}

View File

@@ -3,26 +3,17 @@ package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Log4j2
@Getter
public class FileAttribute {
private int id;
private String language;
private String trackName;
private boolean defaultTrack;
private boolean forcedTrack;
private String type;
private final int id;
private final String language;
private final String trackName;
private final boolean defaultTrack;
private final boolean forcedTrack;
private final LaneType type;
public FileAttribute(int id, String language, String trackName, boolean defaultTrack, boolean forcedTrack, String type) {
public FileAttribute(int id, String language, String trackName, boolean defaultTrack, boolean forcedTrack, LaneType type) {
this.id = id;
this.language = language;
this.trackName = trackName;
@@ -30,28 +21,4 @@ public class FileAttribute {
this.forcedTrack = forcedTrack;
this.type = type;
}
public static boolean pathIsValid(String path) {
File file = new File(path);
if(file.isFile()){
return file.getAbsolutePath().endsWith(".mkv");
}
if(file.isDirectory()){
try(Stream<Path> paths = Files.walk(Paths.get(path))){
List<String> allPaths = paths
.filter(Files::isRegularFile)
.map(f -> f.toAbsolutePath().toString())
.collect(Collectors.toList());
for(String filePath : allPaths){
if(! filePath.endsWith(".mkv")){
return false;
}
}
}catch(IOException e){
log.error("Couldn't find file or directory!", e);
}
return true;
}
return false;
}
}

View File

@@ -0,0 +1,34 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
import lombok.Getter;
import lombok.Setter;
import java.util.Set;
@Getter
@Setter
public class FileInfoDto {
private FileAttribute defaultAudioLane;
private FileAttribute defaultSubtitleLane;
private Set<FileAttribute> desiredForcedSubtitleLanes;
private FileAttribute desiredAudioLane;
private FileAttribute desiredSubtitleLane;
public boolean isChangeNecessary() {
return isAudioDifferent() || isSubtitleDifferent() || areForcedTracksDifferent();
}
public boolean isAudioDifferent() {
return desiredAudioLane != null &&
(defaultAudioLane == null || defaultAudioLane.getId() != desiredAudioLane.getId());
}
public boolean isSubtitleDifferent() {
return desiredSubtitleLane != null &&
(defaultSubtitleLane == null || defaultSubtitleLane.getId() != desiredSubtitleLane.getId());
}
public boolean areForcedTracksDifferent() {
return desiredForcedSubtitleLanes.size() > 0;
}
}

View File

@@ -0,0 +1,14 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
public enum LaneType {
AUDIO,
SUBTITLES;
LaneType() {
}
@Override
public String toString() {
return name();
}
}

View File

@@ -0,0 +1,17 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
public enum MkvToolNix {
MKV_MERGER("mkvmerge.exe"),
MKV_PROP_EDIT("mkvpropedit.exe");
private final String file;
MkvToolNix(String file) {
this.file = file;
}
@Override
public String toString() {
return file;
}
}

View File

@@ -0,0 +1,59 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
import lombok.AccessLevel;
import lombok.Getter;
import java.io.File;
@Getter
public class ResultStatistic {
private static final String result = "Total files: %s%n" +
"├─ Should change: %s%n" +
"├─ Successfully changed: %s%n" +
"├─ Already fit config: %s%n" +
"└─ Failed: %s%n" +
"Runtime: %ss";
private int filesTotal = 0;
private int filesShouldChange = 0;
private int filesSuccessfullyChanged = 0;
private int filesFailed = 0;
private int filesAlreadyFit = 0;
@Getter(AccessLevel.NONE)
private long startTime = 0;
private long runtime = 0;
public synchronized void total() {
filesTotal++;
}
public synchronized void shouldChange() {
filesShouldChange++;
}
public synchronized void success() {
filesSuccessfullyChanged++;
}
public synchronized void failure() {
filesFailed++;
}
public synchronized void fits() {
filesAlreadyFit++;
}
public void startTimer() {
startTime = System.currentTimeMillis();
}
public void stopTimer() {
runtime = System.currentTimeMillis() - startTime;
}
@Override
public String toString() {
return String.format(result, filesTotal, filesShouldChange, filesSuccessfullyChanged, filesAlreadyFit,
filesFailed, runtime / 1000);
}
}

View File

@@ -1,40 +0,0 @@
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.AttributeConfig;
import at.pcgamingfreaks.yaml.YAML;
import at.pcgamingfreaks.yaml.YamlInvalidContentException;
import at.pcgamingfreaks.yaml.YamlKeyNotFoundException;
import lombok.extern.log4j.Log4j2;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
@Log4j2
public class ConfigUtil {
public static List<AttributeConfig> loadConfig() {
try(YAML yaml = new YAML(new File("config.yaml"))){
return yaml.getKeysFiltered(".*audio.*").stream()
.sorted()
.map(elem -> elem.replace(".audio", ""))
.map(elem -> createAttributeConfig(elem, yaml))
.collect(Collectors.toList());
}catch(YamlInvalidContentException | IOException e){
log.fatal("Config could not be loaded");
e.printStackTrace();
}
return null;
}
private static AttributeConfig createAttributeConfig(String key, YAML yaml) {
try{
return new AttributeConfig(
yaml.getStringList(key + ".audio"),
yaml.getStringList(key + ".subtitle"));
}catch(YamlKeyNotFoundException e){
e.printStackTrace();
return null;
}
}
}

View File

@@ -1,12 +1,6 @@
Configuration:
name: DefaultLogger
Appenders:
Console:
name: Console_Out
PatternLayout:
Pattern: "%d{DEFAULT} | %-5level | %thread | %msg %n %throwable"
ThresholdFilter:
level: debug
File:
name: FileAppender
fileName: default.log
@@ -18,11 +12,9 @@ Configuration:
Root:
level: debug
AppenderRef:
- ref: Console_Out
- ref: FileAppender
Logger:
name: "com.zaxxer.hikari.HikariConfig"
level: info
AppenderRef:
- ref: Console_Out
- ref: FileAppender