mirror of
https://github.com/RatzzFatzz/MKVAudioSubtitleChanger.git
synced 2026-02-11 02:05:56 +01:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d86b43388 | |||
| d637558cfe | |||
| 4e53d99c25 | |||
|
|
a19fbac03a | ||
| a5b0224d6c | |||
| f7a2e4234a | |||
| 93f3542cf1 | |||
|
|
b94f6eb7de | ||
| c11431d85b | |||
| 68e5b9e988 | |||
| a1e9031cbc | |||
| 1df06e8a08 | |||
| feaeda746a | |||
| f2fb296698 | |||
| 03efab657c | |||
| bba612476b | |||
|
|
097499a916 | ||
|
|
3617d84b3e | ||
|
|
ecbfc77da0 | ||
|
|
1ed1b6e057 | ||
|
|
013c46d940 | ||
|
|
a41895df1b | ||
|
|
6ba5064ca9 | ||
|
|
9775be153b |
41
README.md
41
README.md
@@ -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:
|
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.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.
|
||||||
|
|
||||||
```
|
```yaml
|
||||||
mkvtoolnixPath: /usr/bin
|
mkvtoolnixPath: C:\Program Files\MKVToolNix
|
||||||
|
# Recommendations for data stored on HDDs, increase when using SSDs
|
||||||
|
threads: 2
|
||||||
|
#forcedKeywords: ["forced", "signs"]
|
||||||
config:
|
config:
|
||||||
1:
|
1:
|
||||||
audio:
|
audio: ger
|
||||||
- jpn
|
subtitle: OFF
|
||||||
subtitle:
|
|
||||||
- ger
|
|
||||||
- eng
|
|
||||||
2:
|
2:
|
||||||
audio:
|
audio: eng
|
||||||
- ger
|
subtitle: ger
|
||||||
- eng
|
|
||||||
subtitle:
|
|
||||||
- OFF
|
|
||||||
```
|
```
|
||||||
This config will first check if there is japanese audio and german or english subtitles available, if yes,
|
Subtitle lanes recognized as forced will be set as one. Already existing ones will not be overwritten or changed.
|
||||||
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.
|
|
||||||
|
|||||||
11
config-template.yaml
Normal file
11
config-template.yaml
Normal 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
|
||||||
14
config.yaml
14
config.yaml
@@ -1,14 +0,0 @@
|
|||||||
mkvtoolnixPath: /usr/bin
|
|
||||||
config:
|
|
||||||
1:
|
|
||||||
audio:
|
|
||||||
- jpn
|
|
||||||
subtitle:
|
|
||||||
- ger
|
|
||||||
- eng
|
|
||||||
2:
|
|
||||||
audio:
|
|
||||||
- ger
|
|
||||||
- eng
|
|
||||||
subtitle:
|
|
||||||
- OFF
|
|
||||||
31
pom.xml
31
pom.xml
@@ -4,9 +4,9 @@
|
|||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<groupId>MKVAudioSubtilesChanger</groupId>
|
<groupId>MKVAudioSubtileChanger</groupId>
|
||||||
<artifactId>MKVAudioSubtitlesChanger</artifactId>
|
<artifactId>MKVAudioSubtitleChanger</artifactId>
|
||||||
<version>1.0</version>
|
<version>2.0</version>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<defaultGoal>clean package</defaultGoal>
|
<defaultGoal>clean package</defaultGoal>
|
||||||
@@ -106,17 +106,17 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-api</artifactId>
|
<artifactId>log4j-api</artifactId>
|
||||||
<version>2.12.0</version>
|
<version>2.17.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-core</artifactId>
|
<artifactId>log4j-core</artifactId>
|
||||||
<version>2.12.0</version>
|
<version>2.17.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-slf4j-impl</artifactId>
|
<artifactId>log4j-slf4j-impl</artifactId>
|
||||||
<version>2.12.0</version>
|
<version>2.17.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
@@ -137,12 +137,27 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
<artifactId>jackson-dataformat-yaml</artifactId>
|
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||||
<version>2.9.9</version>
|
<version>2.13.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<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>
|
</dependency>
|
||||||
<!-- endregion -->
|
<!-- endregion -->
|
||||||
<!-- region unit-tests -->
|
<!-- region unit-tests -->
|
||||||
|
|||||||
@@ -1,42 +1,78 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
|
||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.AttributeConfig;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ConfigProcessor;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.FileCollector;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.intimpl.MkvFileCollector;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.FileProcessor;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
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 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.io.File;
|
||||||
import java.util.ArrayList;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@Log4j2
|
@Log4j2
|
||||||
public class AttributeUpdaterKernel {
|
public class AttributeUpdaterKernel {
|
||||||
MkvFileCollector collector = new MkvFileCollector();
|
|
||||||
|
|
||||||
public void execute(String path) {
|
private final ExecutorService executor = Executors.newFixedThreadPool(Config.getInstance().getThreadCount());
|
||||||
List<AttributeConfig> configPattern = ConfigUtil.loadConfig();
|
private final FileCollector collector;
|
||||||
List<File> allValidPaths = collector.loadFiles(path);
|
private final FileProcessor processor;
|
||||||
if(allValidPaths != null && configPattern != null){
|
private final ResultStatistic statistic = new ResultStatistic();
|
||||||
for(File file : allValidPaths){
|
|
||||||
List<FileAttribute> attributes = collector.loadAttributes(file);
|
public AttributeUpdaterKernel(FileCollector collector, FileProcessor processor) {
|
||||||
boolean fileHasChanged = false;
|
this.collector = collector;
|
||||||
for(AttributeConfig config : configPattern){
|
this.processor = processor;
|
||||||
/*
|
}
|
||||||
* Creating new ArrayList, because the method removes elements from the list by reference
|
|
||||||
*/
|
@SneakyThrows
|
||||||
fileHasChanged = new ConfigProcessor(config).processConfig(file, new ArrayList<>(attributes));
|
public void execute() {
|
||||||
if(fileHasChanged){
|
statistic.startTimer();
|
||||||
break;
|
|
||||||
}
|
try (ProgressBar progressBar = pbBuilder().build()) {
|
||||||
}
|
List<File> files = collector.loadFiles(Config.getInstance().getLibraryPath());
|
||||||
if(! fileHasChanged){
|
progressBar.maxHint(files.size());
|
||||||
log.info(file.getName() + " didn't change!");
|
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{
|
} else {
|
||||||
log.error("Path is not valid or config has errors!");
|
statistic.fits();
|
||||||
}
|
}
|
||||||
|
progressBar.step();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProgressBarBuilder pbBuilder() {
|
||||||
|
return new ProgressBarBuilder()
|
||||||
|
.setStyle(ProgressBarStyle.ASCII)
|
||||||
|
.setUpdateIntervalMillis(250)
|
||||||
|
.setMaxRenderedLength(75);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public class MKVToolProperties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static MKVToolProperties getInstance() {
|
public static MKVToolProperties getInstance() {
|
||||||
if(instance == null){
|
if (instance == null) {
|
||||||
instance = new MKVToolProperties();
|
instance = new MKVToolProperties();
|
||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
|
|||||||
@@ -1,40 +1,62 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
|
||||||
|
|
||||||
import at.pcgamingfreaks.yaml.YAML;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
||||||
import at.pcgamingfreaks.yaml.YamlInvalidContentException;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.MkvFileCollector;
|
||||||
import at.pcgamingfreaks.yaml.YamlKeyNotFoundException;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.MkvFileProcessor;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.apache.commons.cli.*;
|
||||||
|
|
||||||
import java.io.File;
|
import java.util.List;
|
||||||
import java.io.IOException;
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty.*;
|
||||||
|
import static java.lang.Integer.parseInt;
|
||||||
|
|
||||||
@Log4j2
|
@Log4j2
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
if(checkIfMKVToolNixIsValid()){
|
initConfig(args);
|
||||||
AttributeUpdaterKernel kernel = new AttributeUpdaterKernel();
|
AttributeUpdaterKernel kernel = new AttributeUpdaterKernel(new MkvFileCollector(), new MkvFileProcessor());
|
||||||
kernel.execute(args[0]);
|
kernel.execute();
|
||||||
}else{
|
}
|
||||||
log.error("MKVToolNix was not found! Please recheck path");
|
|
||||||
|
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() {
|
private static Option create(String opt, String longOpt, int args, String desc) {
|
||||||
try{
|
Option option = new Option(opt, desc);
|
||||||
String path = new YAML(new File("config.yaml")).getString("mkvtoolnixPath");
|
option.setLongOpt(longOpt);
|
||||||
if(! path.endsWith(File.separator)){
|
option.setArgs(args);
|
||||||
path += File.separator;
|
return option;
|
||||||
}
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,26 +3,17 @@ package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.log4j.Log4j2;
|
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
|
@Log4j2
|
||||||
@Getter
|
@Getter
|
||||||
public class FileAttribute {
|
public class FileAttribute {
|
||||||
private int id;
|
private final int id;
|
||||||
private String language;
|
private final String language;
|
||||||
private String trackName;
|
private final String trackName;
|
||||||
private boolean defaultTrack;
|
private final boolean defaultTrack;
|
||||||
private boolean forcedTrack;
|
private final boolean forcedTrack;
|
||||||
private String type;
|
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.id = id;
|
||||||
this.language = language;
|
this.language = language;
|
||||||
this.trackName = trackName;
|
this.trackName = trackName;
|
||||||
@@ -30,28 +21,4 @@ public class FileAttribute {
|
|||||||
this.forcedTrack = forcedTrack;
|
this.forcedTrack = forcedTrack;
|
||||||
this.type = type;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||||
|
|
||||||
|
public enum LaneType {
|
||||||
|
AUDIO,
|
||||||
|
SUBTITLES;
|
||||||
|
|
||||||
|
LaneType() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
Configuration:
|
Configuration:
|
||||||
name: DefaultLogger
|
name: DefaultLogger
|
||||||
Appenders:
|
Appenders:
|
||||||
Console:
|
|
||||||
name: Console_Out
|
|
||||||
PatternLayout:
|
|
||||||
Pattern: "%d{DEFAULT} | %-5level | %thread | %msg %n %throwable"
|
|
||||||
ThresholdFilter:
|
|
||||||
level: debug
|
|
||||||
File:
|
File:
|
||||||
name: FileAppender
|
name: FileAppender
|
||||||
fileName: default.log
|
fileName: default.log
|
||||||
@@ -18,11 +12,9 @@ Configuration:
|
|||||||
Root:
|
Root:
|
||||||
level: debug
|
level: debug
|
||||||
AppenderRef:
|
AppenderRef:
|
||||||
- ref: Console_Out
|
|
||||||
- ref: FileAppender
|
- ref: FileAppender
|
||||||
Logger:
|
Logger:
|
||||||
name: "com.zaxxer.hikari.HikariConfig"
|
name: "com.zaxxer.hikari.HikariConfig"
|
||||||
level: info
|
level: info
|
||||||
AppenderRef:
|
AppenderRef:
|
||||||
- ref: Console_Out
|
|
||||||
- ref: FileAppender
|
- ref: FileAppender
|
||||||
Reference in New Issue
Block a user