mirror of
https://github.com/RatzzFatzz/MKVAudioSubtitleChanger.git
synced 2026-02-11 02:05:56 +01:00
Compare commits
40 Commits
v3.0-pre.2
...
v3.0.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a075dfb27c | ||
|
|
ed8e592963 | ||
|
|
0a7996f049 | ||
|
|
dd60ca93da | ||
|
|
ba770abb6a | ||
|
|
91f1e8f7bf | ||
| 0fda98426e | |||
| c74cdde442 | |||
| a8551fdbd5 | |||
|
|
b2e9762366 | ||
| cafb12f22a | |||
|
|
f6d65c2d53 | ||
| 1963d1cc5c | |||
| 686a9a0da1 | |||
| e19f780ff0 | |||
| f928cb035e | |||
|
|
fd9a421edc | ||
| 285533bb28 | |||
| 9330deb75f | |||
| 094b772257 | |||
|
|
873f6fca6d | ||
| 4309109583 | |||
| e3baae55d9 | |||
| 7ee51421e0 | |||
|
|
df6a82fd62 | ||
|
|
c551e2e2a5 | ||
| 943308dd59 | |||
| ba4c1bc1fe | |||
|
|
62f75818d9 | ||
| cf64833d3e | |||
| 6372cc560c | |||
| 8317e97639 | |||
| 440251c7c9 | |||
| b07f6894aa | |||
| 73be93a4b6 | |||
| 143206b08c | |||
|
|
80348756f9 | ||
| 773018e3bc | |||
|
|
923b4d06c5 | ||
| d7cd74bfaf |
86
README.md
86
README.md
@@ -1,59 +1,55 @@
|
|||||||
### Table of content
|
## Introduction
|
||||||
- Introduction
|
|
||||||
- Requirements
|
|
||||||
- Running
|
|
||||||
- Configuration
|
|
||||||
- Additional parameters
|
|
||||||
|
|
||||||
### Introduction
|
This program helps to change audio and subtitle tracks of mkv files without rewriting the file. Only track properties will be updated.
|
||||||
|

|
||||||
|
|
||||||
This program helps to change audio and subtitle lines of mkv files.
|
## Requirements
|
||||||
|
|
||||||
### Requirements
|
|
||||||
|
|
||||||
- Java 11 or higher
|
- Java 11 or higher
|
||||||
- mkvtoolnix installation
|
- mkvtoolnix installation
|
||||||
|
|
||||||
### Running
|
## Execution
|
||||||
|
**Minimal usage:**
|
||||||
|
`java -jar mkvaudiosubtitlechanger.jar --library "X:/Files" --attribute-config eng:ger eng:OFF`
|
||||||
|
|
||||||
1. Extract downloaded archive
|
**Safe usage (best for testing before applying to whole library):**
|
||||||
2. Copy `config-template.yaml` to `config.yaml`
|
`java -jar mkvaudiosubtitlechanger.jar --library "X:/Files" --attribute-config eng:ger eng:OFF --safe-mode`
|
||||||
3. Update `config.yaml` to fit your needs
|
|
||||||
4. Open terminal / cmd in the directory of the jar and the config file
|
|
||||||
5. Execute following commands:
|
|
||||||
1. (Optional) `java -jar mkvaudiosubtitleschanger.jar -l [path to mkv or dir with mkv] --safe-mode`
|
|
||||||
2. To permanently apply changes: `java -jar mkvaudiosubtitleschanger.jar -l [path to mkv or dir with mkv]`
|
|
||||||
|
|
||||||
### Configuration
|
Attribute-config must be entered in pairs: `audio:subtitle`; Example: `jpn:eng`. More about this topic
|
||||||
|
[here](https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/wiki/Attribute-Config).
|
||||||
|
|
||||||
Config file needs to be placed in the same directory as the jar or path to config has to be passed via command line
|
## Available parameters
|
||||||
argument.
|
|
||||||
|
|
||||||
The list of language configurations can be expanded. Use `OFF` if you want to turn of the audio or subtitle lane.
|
|
||||||
Players probably will display forced subtitles nonetheless.
|
|
||||||
```yaml
|
|
||||||
config:
|
|
||||||
1:
|
|
||||||
audio: ger
|
|
||||||
subtitle: OFF
|
|
||||||
2:
|
|
||||||
audio: eng
|
|
||||||
subtitle: ger
|
|
||||||
```
|
```
|
||||||
Subtitle lanes recognized as forced will be set as one. Already existing ones will not be overwritten or changed.
|
-l,--library-path <arg> Path to library
|
||||||
|
-a,--attribute-config <arg> Attribute config to decide which tracks to choose when
|
||||||
|
-p,--config-path <arg> Path to config file
|
||||||
### Additional arameters
|
|
||||||
These properties overwrite already existing values in the config file.
|
|
||||||
```properties
|
|
||||||
-c,--config <arg> Path to config file
|
|
||||||
-e,--exclude-directories <arg> Directories to be excluded, combines with config file
|
|
||||||
-h,--help "for help this is" - Yoda
|
|
||||||
-i,--include-pattern <arg> Include files matching pattern
|
|
||||||
-k,--forcedKeywords <arg> Additional keywords to identify forced tracks, combines with config file
|
|
||||||
-l,--library <arg> Path to library
|
|
||||||
-m,--mkvtoolnix <arg> Path to mkv tool nix installation
|
-m,--mkvtoolnix <arg> Path to mkv tool nix installation
|
||||||
-s,--safe-mode Test run (no files will be changes)
|
-s,--safe-mode Test run (no files will be changes)
|
||||||
-t,--threads <arg> thread count (default: 2)
|
-c,--coherent <arg> Try to match all files in dir of depth with the same config
|
||||||
|
-cf,--force-coherent Force coherent and don't update anything if config fits not whole config (default: false)
|
||||||
|
-n,--only-new-files Sets filter-date to last successful execution (Overwrites input of filter-date)
|
||||||
|
-d,--filter-date <arg> Only consider files created newer than entered date (format: "dd.MM.yyyy-HH:mm:ss")
|
||||||
|
-t,--threads <arg> Thread count (default: 2)
|
||||||
|
-i,--include-pattern <arg> Include files matching pattern (default: ".*")
|
||||||
|
-e,--excluded-directories <arg> Directories to be excluded, combines with config file
|
||||||
|
-fk,--forced-keywords <arg> Additional keywords to identify forced tracks
|
||||||
|
-ck,--commentary-keywords <arg> Additional keywords to identify commentary tracks
|
||||||
|
-ps,--preferred-subtitles <arg> Additional keywords to prefer specific subtitle tracks
|
||||||
-v,--version Display version
|
-v,--version Display version
|
||||||
|
-h,--help "For help this is" - Yoda
|
||||||
|
```
|
||||||
|
If you need more information about how each parameter works, check out [this wiki page](https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/wiki/Parameters).
|
||||||
|
|
||||||
|
All parameters can also be defined in a [config file](https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/wiki/How-to-config-file).
|
||||||
|
|
||||||
|
## Build requirements
|
||||||
|
- JDK 11 or higher
|
||||||
|
- Maven 3
|
||||||
|
- Git
|
||||||
|
|
||||||
|
## Build from source
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/RatzzFatzz/MKVAudioSubtitleChanger.git
|
||||||
|
cd MKVAudioSubtitleChanger
|
||||||
|
mvn package
|
||||||
```
|
```
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
mkvtoolnix: C:\Program Files\MKVToolNix
|
mkvtoolnix: C:\Program Files\MKVToolNix
|
||||||
|
library: X:/Files
|
||||||
|
|
||||||
config:
|
attribute-config:
|
||||||
1:
|
1:
|
||||||
audio: ger
|
audio: ger
|
||||||
subtitle: OFF
|
subtitle: OFF
|
||||||
@@ -10,9 +11,24 @@ config:
|
|||||||
|
|
||||||
# Recommendations for data stored on HDDs, increase when using SSDs
|
# Recommendations for data stored on HDDs, increase when using SSDs
|
||||||
#threads: 2
|
#threads: 2
|
||||||
#forcedKeywords: ["forced", "signs"]
|
|
||||||
|
#forced-keywords: ["forced", "signs"]
|
||||||
|
#commentary-keywords: ["commentary", "director"]
|
||||||
|
#preferred-subtitles: ["unstyled"]
|
||||||
|
|
||||||
#exclude-directories:
|
#exclude-directories:
|
||||||
# - "D:/Path/To/File.mkv"
|
# - "D:/Path/To/File.mkv"
|
||||||
# - "D:/Path/To/Directory"
|
# - "D:/Path/To/Directory"
|
||||||
|
|
||||||
|
# If pattern is negated, can be used to exclude files
|
||||||
#include-pattern: "regex"
|
#include-pattern: "regex"
|
||||||
|
|
||||||
|
# Only files newer than
|
||||||
|
#filter-date: 20.03.2021-10:11:12
|
||||||
|
|
||||||
|
safe-mode:
|
||||||
|
#coherent:
|
||||||
|
#force-coherent:
|
||||||
|
#only-new-files:
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
BIN
example.gif
Normal file
BIN
example.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
52
pom.xml
52
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>MKVAudioSubtileChanger</groupId>
|
<groupId>at.pcgamingfreaks</groupId>
|
||||||
<artifactId>MKVAudioSubtitleChanger</artifactId>
|
<artifactId>MKVAudioSubtitleChanger</artifactId>
|
||||||
<version>3.0</version>
|
<version>3.0.2</version>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<defaultGoal>clean package</defaultGoal>
|
<defaultGoal>clean package</defaultGoal>
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<directory>./</directory>
|
<directory>./</directory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>language-codes</include>
|
<include>language-codes</include>
|
||||||
<include>version.properties</include>
|
<include>project.properties</include>
|
||||||
</includes>
|
</includes>
|
||||||
<filtering>true</filtering>
|
<filtering>true</filtering>
|
||||||
</resource>
|
</resource>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
<version>3.1.1</version>
|
<version>3.2.2</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<archive>
|
<archive>
|
||||||
<manifestEntries>
|
<manifestEntries>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
<version>3.2.1</version>
|
<version>3.4.1</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
@@ -62,6 +62,13 @@
|
|||||||
<include>*:*</include>
|
<include>*:*</include>
|
||||||
</includes>
|
</includes>
|
||||||
</artifactSet>
|
</artifactSet>
|
||||||
|
<transformers>
|
||||||
|
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
|
<manifestEntries>
|
||||||
|
<Multi-Release>true</Multi-Release>
|
||||||
|
</manifestEntries>
|
||||||
|
</transformer>
|
||||||
|
</transformers>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
@@ -124,33 +131,18 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-api</artifactId>
|
<artifactId>log4j-api</artifactId>
|
||||||
<version>2.17.1</version>
|
<version>2.18.0</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.17.1</version>
|
<version>2.18.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j18-impl -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-slf4j-impl</artifactId>
|
<artifactId>log4j-slf4j18-impl</artifactId>
|
||||||
<version>2.17.1</version>
|
<version>2.18.0</version>
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>slf4j-log4j12</artifactId>
|
|
||||||
<version>1.7.28</version>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>jcl-over-slf4j</artifactId>
|
|
||||||
<version>1.7.28</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.slf4j</groupId>
|
|
||||||
<artifactId>jul-to-slf4j</artifactId>
|
|
||||||
<version>1.7.28</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
@@ -160,7 +152,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.core</groupId>
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
<artifactId>jackson-databind</artifactId>
|
<artifactId>jackson-databind</artifactId>
|
||||||
<version>2.13.4.1</version>
|
<version>2.13.4.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-cli</groupId>
|
<groupId>commons-cli</groupId>
|
||||||
@@ -175,7 +167,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>me.tongfei</groupId>
|
<groupId>me.tongfei</groupId>
|
||||||
<artifactId>progressbar</artifactId>
|
<artifactId>progressbar</artifactId>
|
||||||
<version>0.9.3</version>
|
<version>0.9.5</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- endregion -->
|
<!-- endregion -->
|
||||||
<!-- region unit-tests -->
|
<!-- region unit-tests -->
|
||||||
@@ -210,6 +202,12 @@
|
|||||||
<artifactId>YAML-Parser</artifactId>
|
<artifactId>YAML-Parser</artifactId>
|
||||||
<version>2.0-SNAPSHOT</version>
|
<version>2.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/net.harawata/appdirs -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.harawata</groupId>
|
||||||
|
<artifactId>appdirs</artifactId>
|
||||||
|
<version>1.2.1</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
2
project.properties
Normal file
2
project.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
version=${project.version}
|
||||||
|
project_name=${project.artifactId}
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
|
|
||||||
|
|
||||||
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.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.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Log4j2
|
|
||||||
public class AttributeUpdaterKernel {
|
|
||||||
|
|
||||||
private final ExecutorService executor = Executors.newFixedThreadPool(Config.getInstance().getThreads());
|
|
||||||
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> excludedFiles = Config.getInstance().getExcludedDirectories().stream()
|
|
||||||
.map(collector::loadFiles)
|
|
||||||
.flatMap(Collection::stream)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
List<File> files = collector.loadFiles(Config.getInstance().getLibraryPath().getAbsolutePath()).stream()
|
|
||||||
.filter(file -> !excludedFiles.contains(file))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
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);
|
|
||||||
log.info(statistic);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void process(File file, ProgressBar progressBar) {
|
|
||||||
List<FileAttribute> attributes = processor.loadAttributes(file);
|
|
||||||
FileInfoDto fileInfo = processor.filterAttributes(attributes);
|
|
||||||
statistic.total();
|
|
||||||
if (fileInfo.isChangeNecessary()) {
|
|
||||||
statistic.shouldChange();
|
|
||||||
if (!Config.getInstance().isSafeMode()) {
|
|
||||||
try {
|
|
||||||
processor.update(file, fileInfo);
|
|
||||||
statistic.success();
|
|
||||||
log.info("Updated {}", file.getAbsolutePath());
|
|
||||||
} catch (IOException | RuntimeException e) {
|
|
||||||
statistic.failedChanging();
|
|
||||||
log.warn("File couldn't be updated: '{}', Error: {}", file.getAbsoluteFile(), e.getMessage().replaceAll("\\r|\\n", " "));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (fileInfo.isUnableToApplyConfig()) {
|
|
||||||
statistic.noSuitableConfigFound();
|
|
||||||
} else if (fileInfo.isAlreadySuitable()){
|
|
||||||
statistic.alreadyFits();
|
|
||||||
} else {
|
|
||||||
statistic.failure();
|
|
||||||
}
|
|
||||||
progressBar.step();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ProgressBarBuilder pbBuilder() {
|
|
||||||
return new ProgressBarBuilder()
|
|
||||||
.setStyle(ProgressBarStyle.ASCII)
|
|
||||||
.setUpdateIntervalMillis(250)
|
|
||||||
.setMaxRenderedLength(75);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,15 +2,20 @@ package at.pcgamingfreaks.mkvaudiosubtitlechanger;
|
|||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ConfigLoader;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ConfigLoader;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.CachedMkvFileProcessor;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.kernel.AttributeUpdaterKernel;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.kernel.CoherentAttributeUpdaterKernel;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.kernel.DefaultAttributeUpdaterKernel;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.MkvFileCollector;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.MkvFileCollector;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.MkvFileProcessor;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
|
|
||||||
@Log4j2
|
@Slf4j
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
ConfigLoader.initConfig(args);
|
ConfigLoader.initConfig(args);
|
||||||
AttributeUpdaterKernel kernel = new AttributeUpdaterKernel(new MkvFileCollector(), new MkvFileProcessor());
|
AttributeUpdaterKernel kernel = Config.getInstance().getCoherent() != null
|
||||||
|
? new CoherentAttributeUpdaterKernel(new MkvFileCollector(), new CachedMkvFileProcessor())
|
||||||
|
: new DefaultAttributeUpdaterKernel(new MkvFileCollector(), new CachedMkvFileProcessor());
|
||||||
kernel.execute();
|
kernel.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import lombok.AccessLevel;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.cli.CommandLineParser;
|
import org.apache.commons.cli.CommandLineParser;
|
||||||
import org.apache.commons.cli.DefaultParser;
|
import org.apache.commons.cli.DefaultParser;
|
||||||
import org.apache.commons.cli.HelpFormatter;
|
import org.apache.commons.cli.HelpFormatter;
|
||||||
@@ -15,7 +15,7 @@ import java.io.File;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@Log4j2
|
@Slf4j
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
@@ -36,9 +36,15 @@ public class Config {
|
|||||||
private Pattern includePattern;
|
private Pattern includePattern;
|
||||||
private boolean safeMode;
|
private boolean safeMode;
|
||||||
|
|
||||||
private Set<String> forcedKeywords = new HashSet<>(Arrays.asList("forced", "signs"));
|
private Integer coherent;
|
||||||
|
private boolean forceCoherent;
|
||||||
|
private boolean onlyNewFiles;
|
||||||
|
private Date filterDate;
|
||||||
|
|
||||||
|
private Set<String> forcedKeywords = new HashSet<>(Arrays.asList("forced", "signs", "songs"));
|
||||||
private Set<String> commentaryKeywords = new HashSet<>(Arrays.asList("commentary", "director"));
|
private Set<String> commentaryKeywords = new HashSet<>(Arrays.asList("commentary", "director"));
|
||||||
private Set<String> excludedDirectories = new HashSet<>();
|
private Set<String> excludedDirectories = new HashSet<>();
|
||||||
|
private Set<String> preferredSubtitles = new HashSet<>(Arrays.asList("unstyled"));
|
||||||
|
|
||||||
private List<AttributeConfig> attributeConfig;
|
private List<AttributeConfig> attributeConfig;
|
||||||
|
|
||||||
@@ -63,20 +69,29 @@ public class Config {
|
|||||||
mkvToolNix.getAbsolutePath() + "/" + application;
|
mkvToolNix.getAbsolutePath() + "/" + application;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getNormalizedLibraryPath() {
|
||||||
|
return this.getLibraryPath().getAbsolutePath().replace("\\", "/");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new StringJoiner(", ", Config.class.getSimpleName() + "[", "]")
|
return new StringJoiner(", ", Config.class.getSimpleName() + "[", "]")
|
||||||
.add("parser=" + parser).add("\n")
|
.add("parser=" + parser)
|
||||||
.add("formatter=" + formatter).add("\n")
|
.add("formatter=" + formatter)
|
||||||
.add("configPath=" + configPath).add("\n")
|
.add("configPath=" + configPath)
|
||||||
.add("libraryPath=" + libraryPath).add("\n")
|
.add("libraryPath=" + libraryPath)
|
||||||
.add("isSafeMode=" + safeMode).add("\n")
|
.add("mkvToolNix=" + mkvToolNix)
|
||||||
.add("forcedKeywords=" + forcedKeywords).add("\n")
|
.add("threads=" + threads)
|
||||||
.add("commentaryKeywords=" + commentaryKeywords).add("\n")
|
.add("includePattern=" + includePattern)
|
||||||
.add("excludedDirectories=" + excludedDirectories).add("\n")
|
.add("safeMode=" + safeMode)
|
||||||
.add("threadCount=" + threads).add("\n")
|
.add("coherent=" + coherent)
|
||||||
.add("includePattern=" + includePattern).add("\n")
|
.add("forceCoherent=" + forceCoherent)
|
||||||
.add("mkvToolNixPath='" + mkvToolNix + "'").add("\n")
|
.add("onlyNewFiles=" + onlyNewFiles)
|
||||||
|
.add("filterDate=" + filterDate)
|
||||||
|
.add("forcedKeywords=" + forcedKeywords)
|
||||||
|
.add("commentaryKeywords=" + commentaryKeywords)
|
||||||
|
.add("excludedDirectories=" + excludedDirectories)
|
||||||
|
.add("preferredSubtitles=" + preferredSubtitles)
|
||||||
.add("attributeConfig=" + attributeConfig)
|
.add("attributeConfig=" + attributeConfig)
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
|
|||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validator.*;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validator.*;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.VersionUtil;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.ProjectUtil;
|
||||||
import at.pcgamingfreaks.yaml.YAML;
|
import at.pcgamingfreaks.yaml.YAML;
|
||||||
import at.pcgamingfreaks.yaml.YamlInvalidContentException;
|
import at.pcgamingfreaks.yaml.YamlInvalidContentException;
|
||||||
import org.apache.commons.cli.*;
|
import org.apache.commons.cli.*;
|
||||||
@@ -11,25 +11,33 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty.*;
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty.*;
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.CommandLineOptionsUtil.optionOf;
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.CommandLineOptionsUtil.optionOf;
|
||||||
|
|
||||||
public class ConfigLoader {
|
public class ConfigLoader {
|
||||||
private static final List<ConfigValidator<?>> VALIDATORS = List.of(
|
private static final List<ConfigValidator<?>> VALIDATORS = Stream.of(
|
||||||
new ConfigPathValidator(CONFIG_PATH, false),
|
new ConfigPathValidator(CONFIG_PATH, false),
|
||||||
new PathValidator(LIBRARY, true, null),
|
new PathValidator(LIBRARY, true, null),
|
||||||
new ThreadValidator(THREADS, false, 2),
|
new ThreadValidator(THREADS, false, 2),
|
||||||
new MkvToolNixPathValidator(MKV_TOOL_NIX, true, Path.of("C:\\Program Files\\MKVToolNix").toFile()),
|
new MkvToolNixPathValidator(MKV_TOOL_NIX, true, Path.of("C:\\Program Files\\MKVToolNix").toFile()),
|
||||||
new BooleanValidator(SAFE_MODE, false),
|
new BooleanValidator(SAFE_MODE, false),
|
||||||
|
new BooleanValidator(ONLY_NEW_FILES, false),
|
||||||
|
new DateValidator(FILTER_DATE, false),
|
||||||
new PatternValidator(INCLUDE_PATTERN, false, Pattern.compile(".*")),
|
new PatternValidator(INCLUDE_PATTERN, false, Pattern.compile(".*")),
|
||||||
new SetValidator(FORCED_KEYWORDS, false, true),
|
new SetValidator(FORCED_KEYWORDS, false, true),
|
||||||
new SetValidator(COMMENTARY_KEYWORDS, false, true),
|
new SetValidator(COMMENTARY_KEYWORDS, false, true),
|
||||||
new SetValidator(EXCLUDE_DIRECTORY, false, true),
|
new SetValidator(EXCLUDED_DIRECTORY, false, true),
|
||||||
new AttributeConfigValidator()
|
new SetValidator(PREFERRED_SUBTITLES, false, true),
|
||||||
);
|
new AttributeConfigValidator(),
|
||||||
|
new CoherentConfigValidator(COHERENT, false),
|
||||||
|
new BooleanValidator(FORCE_COHERENT, false)
|
||||||
|
).sorted(Comparator.comparing((ConfigValidator<?> validator) -> validator.getWeight()).reversed()).collect(Collectors.toList());
|
||||||
|
|
||||||
public static void initConfig(String[] args) {
|
public static void initConfig(String[] args) {
|
||||||
HelpFormatter formatter = new HelpFormatter();
|
HelpFormatter formatter = new HelpFormatter();
|
||||||
@@ -55,7 +63,7 @@ public class ConfigLoader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (results.contains(ValidationResult.INVALID)) System.exit(1);
|
if (results.contains(ValidationResult.INVALID) || results.contains(ValidationResult.MISSING)) System.exit(1);
|
||||||
System.out.println();
|
System.out.println();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +84,7 @@ public class ConfigLoader {
|
|||||||
if (cmd == null) throw new NullPointerException();
|
if (cmd == null) throw new NullPointerException();
|
||||||
return cmd;
|
return cmd;
|
||||||
} catch (ParseException | NullPointerException e) {
|
} catch (ParseException | NullPointerException e) {
|
||||||
formatter.printHelp(106, "java -jar MKVAudioSubtitlesChanger.jar -l <path_to_library>",
|
formatter.printHelp(130, "java -jar MKVAudioSubtitlesChanger.jar -l <path_to_library>",
|
||||||
"\nParameters:", options,
|
"\nParameters:", options,
|
||||||
"\nFeature requests and bug reports: https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/issues");
|
"\nFeature requests and bug reports: https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/issues");
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
@@ -85,8 +93,8 @@ public class ConfigLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void exitIfHelp(CommandLine cmd, Options options, HelpFormatter formatter) {
|
private static void exitIfHelp(CommandLine cmd, Options options, HelpFormatter formatter) {
|
||||||
if (cmd.hasOption("help")) {
|
if (cmd.hasOption(HELP.prop())) {
|
||||||
formatter.printHelp(106, "java -jar MKVAudioSubtitlesChanger.jar -l <path_to_library>",
|
formatter.printHelp(130, "java -jar MKVAudioSubtitlesChanger.jar -l <path_to_library>",
|
||||||
"\nParameters:", options,
|
"\nParameters:", options,
|
||||||
"\nFeature requests and bug reports: https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/issues");
|
"\nFeature requests and bug reports: https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/issues");
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
@@ -95,7 +103,7 @@ public class ConfigLoader {
|
|||||||
|
|
||||||
private static void exitIfVersion(CommandLine cmd) {
|
private static void exitIfVersion(CommandLine cmd) {
|
||||||
if (cmd.hasOption(VERSION.prop())) {
|
if (cmd.hasOption(VERSION.prop())) {
|
||||||
System.out.printf("MKV Audio Subtitle Changer Version %s%n", VersionUtil.getVersion());
|
System.out.printf("MKV Audio Subtitle Changer Version %s%n", ProjectUtil.getVersion());
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import java.util.List;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.LanguageValidatorUtil.isAudioLanguageValid;
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.LanguageValidatorUtil.isLanguageValid;
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.LanguageValidatorUtil.isLanguageValid;
|
||||||
|
|
||||||
public class AttributeConfigValidator extends ConfigValidator<List<AttributeConfig>> {
|
public class AttributeConfigValidator extends ConfigValidator<List<AttributeConfig>> {
|
||||||
@@ -78,7 +79,7 @@ public class AttributeConfigValidator extends ConfigValidator<List<AttributeConf
|
|||||||
}
|
}
|
||||||
boolean isValid;
|
boolean isValid;
|
||||||
for (AttributeConfig attributeConfig : result) {
|
for (AttributeConfig attributeConfig : result) {
|
||||||
isValid = isLanguageValid(attributeConfig.getAudioLanguage())
|
isValid = isAudioLanguageValid(attributeConfig.getAudioLanguage())
|
||||||
&& isLanguageValid(attributeConfig.getSubtitleLanguage());
|
&& isLanguageValid(attributeConfig.getSubtitleLanguage());
|
||||||
if (!isValid) return false;
|
if (!isValid) return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validator;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty;
|
||||||
|
import org.apache.commons.lang3.math.NumberUtils;
|
||||||
|
|
||||||
|
public class CoherentConfigValidator extends ConfigValidator<Integer> {
|
||||||
|
private static final Integer DISABLED = -1;
|
||||||
|
|
||||||
|
public CoherentConfigValidator(ConfigProperty property, boolean required) {
|
||||||
|
super(property, required, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Integer parse(String value) {
|
||||||
|
return NumberUtils.isParsable(value) ? Integer.parseInt(value) : DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isValid(Integer result) {
|
||||||
|
return result >= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,4 +27,9 @@ public class ConfigPathValidator extends PathValidator {
|
|||||||
protected boolean isValid(File result) {
|
protected boolean isValid(File result) {
|
||||||
return super.isValid(result) && (result.getAbsolutePath().endsWith(".yml") || result.getAbsolutePath().endsWith(".yaml"));
|
return super.isValid(result) && (result.getAbsolutePath().endsWith(".yml") || result.getAbsolutePath().endsWith(".yaml"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWeight() {
|
||||||
|
return 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,18 @@ import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty;
|
|||||||
import at.pcgamingfreaks.yaml.YAML;
|
import at.pcgamingfreaks.yaml.YAML;
|
||||||
import at.pcgamingfreaks.yaml.YamlKeyNotFoundException;
|
import at.pcgamingfreaks.yaml.YamlKeyNotFoundException;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.cli.CommandLine;
|
import org.apache.commons.cli.CommandLine;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.StringJoiner;
|
||||||
import java.util.function.BiFunction;
|
import java.util.function.BiFunction;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public abstract class ConfigValidator<FieldType> {
|
public abstract class ConfigValidator<FieldType> {
|
||||||
protected final ConfigProperty property;
|
protected final ConfigProperty property;
|
||||||
@@ -38,7 +38,9 @@ public abstract class ConfigValidator<FieldType> {
|
|||||||
Optional<FieldType> cmdResult = provideDataCmd().apply(cmd, property);
|
Optional<FieldType> cmdResult = provideDataCmd().apply(cmd, property);
|
||||||
Optional<FieldType> yamlResult = provideDataYaml().apply(yaml, property);
|
Optional<FieldType> yamlResult = provideDataYaml().apply(yaml, property);
|
||||||
|
|
||||||
if (cmdResult.isPresent()) {
|
if (isOverwritingNecessary()) {
|
||||||
|
result = overwriteValue();
|
||||||
|
} else if (cmdResult.isPresent()) {
|
||||||
result = cmdResult.get();
|
result = cmdResult.get();
|
||||||
} else if (yamlResult.isPresent()) {
|
} else if (yamlResult.isPresent()) {
|
||||||
result = yamlResult.get();
|
result = yamlResult.get();
|
||||||
@@ -98,6 +100,20 @@ public abstract class ConfigValidator<FieldType> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if overwriting this property is necessary.
|
||||||
|
*/
|
||||||
|
protected boolean isOverwritingNecessary() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@link FieldType} to overwrite result with.
|
||||||
|
*/
|
||||||
|
protected FieldType overwriteValue() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse input parameter to desired format.
|
* Parse input parameter to desired format.
|
||||||
*
|
*
|
||||||
@@ -121,29 +137,40 @@ public abstract class ConfigValidator<FieldType> {
|
|||||||
* @return false if method invocation failed
|
* @return false if method invocation failed
|
||||||
*/
|
*/
|
||||||
protected boolean setValue(FieldType result) {
|
protected boolean setValue(FieldType result) {
|
||||||
List<Method> methods = Arrays.stream(Config.getInstance().getClass().getDeclaredMethods())
|
for (Method method : Config.getInstance().getClass().getDeclaredMethods()) {
|
||||||
.filter(containsSetterOf(property))
|
if (containsSetterOf(property).test(method)) {
|
||||||
.collect(Collectors.toList());
|
|
||||||
if (methods.size() != 1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
methods.get(0).invoke(Config.getInstance(), result);
|
method.invoke(Config.getInstance(), result);
|
||||||
|
return true;
|
||||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
return true;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Predicate<Method> containsSetterOf(ConfigProperty property) {
|
protected Predicate<Method> containsSetterOf(ConfigProperty property) {
|
||||||
return method -> StringUtils.containsIgnoreCase(method.getName(), "set")
|
return method -> StringUtils.startsWith(method.getName(), "set")
|
||||||
&& StringUtils.containsIgnoreCase(method.getName(), property.prop().replace("-", ""));
|
&& StringUtils.equalsIgnoreCase(method.getName().replace("set", ""), property.prop().replace("-", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Predicate<Method> containsGetterOf(ConfigProperty property) {
|
protected Predicate<Method> containsGetterOf(ConfigProperty property) {
|
||||||
return method -> StringUtils.containsIgnoreCase(method.getName(), "get")
|
return method -> StringUtils.startsWith(method.getName(), "get")
|
||||||
&& StringUtils.containsIgnoreCase(method.getName(), property.prop().replace("-", ""));
|
&& StringUtils.equalsIgnoreCase(method.getName().replace("get", ""), property.prop().replace("-", ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getWeight() {
|
||||||
|
return 50;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new StringJoiner(", ", ConfigValidator.class.getSimpleName() + "[", "]")
|
||||||
|
.add("property=" + property)
|
||||||
|
.add("required=" + required)
|
||||||
|
.add("defaultValue=" + defaultValue)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.validator;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.DateUtils;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.ProjectUtil;
|
||||||
|
import at.pcgamingfreaks.yaml.YAML;
|
||||||
|
import at.pcgamingfreaks.yaml.YamlInvalidContentException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import net.harawata.appdirs.AppDirsFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class DateValidator extends ConfigValidator<Date> {
|
||||||
|
private static final Date INVALID_DATE = new Date(0);
|
||||||
|
private static final Date DEFAULT_DATE = new Date(1000);
|
||||||
|
|
||||||
|
public DateValidator(ConfigProperty property, boolean required) {
|
||||||
|
super(property, required, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean isOverwritingNecessary() {
|
||||||
|
return Config.getInstance().isOnlyNewFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Date overwriteValue() {
|
||||||
|
try {
|
||||||
|
String filePath = AppDirsFactory.getInstance().getUserConfigDir(ProjectUtil.getProjectName(), null, null);
|
||||||
|
File lastExecutionFile = Path.of(filePath + "/last-execution.yml").toFile();
|
||||||
|
if (!lastExecutionFile.exists()) {
|
||||||
|
return DEFAULT_DATE;
|
||||||
|
}
|
||||||
|
YAML yaml = new YAML(lastExecutionFile);
|
||||||
|
return parse(yaml.getString(Config.getInstance().getNormalizedLibraryPath(), DateUtils.convert(DEFAULT_DATE)));
|
||||||
|
} catch (YamlInvalidContentException | IOException e) {
|
||||||
|
log.error("Couldn't open last-execution.properties");
|
||||||
|
return INVALID_DATE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Date parse(String value) {
|
||||||
|
return DateUtils.convert(value, INVALID_DATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean isValid(Date result) {
|
||||||
|
return !result.equals(INVALID_DATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWeight() {
|
||||||
|
return 40;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.exceptions;
|
||||||
|
|
||||||
|
public class MkvToolNixException extends RuntimeException{
|
||||||
|
|
||||||
|
public MkvToolNixException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return super.getMessage().replaceAll("\\r|\\n", " ");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class Cache<Key, Value> {
|
||||||
|
private final Map<Key, Value> cache = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve {@link Value} from Cache or run creationFunction and return its value.
|
||||||
|
* @param key key of cache map
|
||||||
|
* @param creationFunction function to create missing values
|
||||||
|
* @return {@link Value} from Cache, or if missing result from creationFunction.
|
||||||
|
*/
|
||||||
|
public synchronized Value retrieve(Key key, Function<Key, Value> creationFunction) {
|
||||||
|
return cache.computeIfAbsent(key, creationFunction::apply);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CachedMkvFileProcessor extends MkvFileProcessor {
|
||||||
|
Cache<File, List<FileAttribute>> cache = new Cache<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<FileAttribute> loadAttributes(File file) {
|
||||||
|
return cache.retrieve(file, super::loadAttributes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,4 +10,13 @@ public interface FileCollector {
|
|||||||
* @return list of all files within the directory
|
* @return list of all files within the directory
|
||||||
*/
|
*/
|
||||||
List<File> loadFiles(String path);
|
List<File> loadFiles(String path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all directories from path, but only until depth is reached.
|
||||||
|
*
|
||||||
|
* @param path leads to a directory which will be loaded recursively until depth
|
||||||
|
* @param depth limit directory crawling
|
||||||
|
* @return list of directory until depth
|
||||||
|
*/
|
||||||
|
List<File> loadDirectories(String path, int depth);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,52 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ResultStatistic;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.DateUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
public class FileFilter {
|
public class FileFilter {
|
||||||
static boolean accept(File pathName, String[] fileExtensions) {
|
static boolean accept(File pathName, String[] fileExtensions) {
|
||||||
return StringUtils.endsWithAny(pathName.getAbsolutePath().toLowerCase(), fileExtensions)
|
if (hasProperFileExtension(pathName, fileExtensions)
|
||||||
&& Config.getInstance().getIncludePattern().matcher(pathName.getName()).matches();
|
&& hasMatchingPattern(pathName)
|
||||||
|
&& isNewer(pathName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultStatistic.getInstance().total();
|
||||||
|
ResultStatistic.getInstance().excluded();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasProperFileExtension(File pathName, String[] fileExtensions) {
|
||||||
|
return StringUtils.endsWithAny(pathName.getAbsolutePath().toLowerCase(), fileExtensions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasMatchingPattern(File pathName) {
|
||||||
|
return Config.getInstance().getIncludePattern().matcher(pathName.getName()).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNewer(File pathName) {
|
||||||
|
Config config = Config.getInstance();
|
||||||
|
if (config.getFilterDate() == null) return true;
|
||||||
|
try {
|
||||||
|
BasicFileAttributes attributes = Files.readAttributes(pathName.toPath(), BasicFileAttributes.class);
|
||||||
|
return isNewer(DateUtils.convert(attributes.creationTime().toMillis()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.warn("File attributes could not be read.", e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isNewer(Date creationDate) {
|
||||||
|
return creationDate.toInstant().isAfter(Config.getInstance().getFilterDate().toInstant());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.exceptions.MkvToolNixException;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfoDto;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfoDto;
|
||||||
|
|
||||||
@@ -10,12 +12,40 @@ import java.util.List;
|
|||||||
public interface FileProcessor {
|
public interface FileProcessor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Load track information from file.
|
||||||
|
*
|
||||||
* @param file Takes the file from which the attributes will be returned
|
* @param file Takes the file from which the attributes will be returned
|
||||||
* @return list of all important attributes
|
* @return list of all important attributes
|
||||||
*/
|
*/
|
||||||
List<FileAttribute> loadAttributes(File file);
|
List<FileAttribute> loadAttributes(File file);
|
||||||
|
|
||||||
FileInfoDto filterAttributes(List<FileAttribute> attributes);
|
/**
|
||||||
|
* Populate FileInfoDto with the currently set default tracks.
|
||||||
|
* @param info to be populated
|
||||||
|
* @param attributes Track information of FileInfoDto
|
||||||
|
* @param nonForcedTracks List of all not forced tracks
|
||||||
|
*/
|
||||||
|
void detectDefaultTracks(FileInfoDto info, List<FileAttribute> attributes, List<FileAttribute> nonForcedTracks);
|
||||||
|
|
||||||
void update(File file, FileInfoDto fileInfo) throws IOException;
|
/**
|
||||||
|
* Populate FileInfoDto with the desired tracks, based on AttributeConfig.
|
||||||
|
* @param info to be populated
|
||||||
|
* @param nonForcedTracks List of all non-forced tracks
|
||||||
|
* @param nonCommentaryTracks List of all non-commentary tracks
|
||||||
|
*/
|
||||||
|
void detectDesiredTracks(FileInfoDto info, List<FileAttribute> nonForcedTracks, List<FileAttribute> nonCommentaryTracks,
|
||||||
|
AttributeConfig... configs);
|
||||||
|
|
||||||
|
List<FileAttribute> retrieveNonForcedTracks(List<FileAttribute> attributes);
|
||||||
|
|
||||||
|
List<FileAttribute> retrieveNonCommentaryTracks(List<FileAttribute> attributes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the file.
|
||||||
|
* @param file to be updated
|
||||||
|
* @param fileInfo information to update file
|
||||||
|
* @throws IOException when error occurs accessing file retrieving information
|
||||||
|
* @throws MkvToolNixException when error occurs while sending query to mkvpropedit
|
||||||
|
*/
|
||||||
|
void update(File file, FileInfoDto fileInfo) throws IOException, MkvToolNixException;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -12,10 +12,13 @@ import java.util.List;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Log4j2
|
@Slf4j
|
||||||
public class MkvFileCollector implements FileCollector {
|
public class MkvFileCollector implements FileCollector {
|
||||||
private static final String[] fileExtensions = new String[]{".mkv", ".mka", ".mks", ".mk3d"};
|
private static final String[] fileExtensions = new String[]{".mkv", ".mka", ".mks", ".mk3d"};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public List<File> loadFiles(String path) {
|
public List<File> loadFiles(String path) {
|
||||||
try (Stream<Path> paths = Files.walk(Paths.get(path))) {
|
try (Stream<Path> paths = Files.walk(Paths.get(path))) {
|
||||||
@@ -28,4 +31,19 @@ public class MkvFileCollector implements FileCollector {
|
|||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<File> loadDirectories(String path, int depth) {
|
||||||
|
try (Stream<Path> paths = Files.walk(Paths.get(path), depth)) {
|
||||||
|
return paths.map(Path::toFile)
|
||||||
|
.filter(File::isDirectory)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Couldn't find file or directory!", e);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.exceptions.MkvToolNixException;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.*;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.*;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.SetUtils;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.SetUtils;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.logging.log4j.core.util.IOUtils;
|
import org.apache.logging.log4j.core.util.IOUtils;
|
||||||
|
|
||||||
@@ -14,19 +15,24 @@ import java.io.InputStream;
|
|||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.LaneType.AUDIO;
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.LaneType.AUDIO;
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.LaneType.SUBTITLES;
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.LaneType.SUBTITLES;
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
|
|
||||||
@Log4j2
|
@Slf4j
|
||||||
public class MkvFileProcessor implements FileProcessor {
|
public class MkvFileProcessor implements FileProcessor {
|
||||||
private final ObjectMapper mapper = new ObjectMapper();
|
private final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
private static final SubtitleTrackComparator subtitleTrackComparator =
|
||||||
|
new SubtitleTrackComparator(Config.getInstance().getPreferredSubtitles().toArray(new String[0]));
|
||||||
|
|
||||||
private static final String DISABLE_DEFAULT_TRACK = "--edit track:%s --set flag-default=0 ";
|
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_DEFAULT_TRACK = "--edit track:%s --set flag-default=1 ";
|
||||||
|
private static final String DISABLE_FORCED_TRACK = "--edit track:%s --set flag-forced=0 ";
|
||||||
private static final String ENABLE_FORCED_TRACK = "--edit track:%s --set flag-forced=1 ";
|
private static final String ENABLE_FORCED_TRACK = "--edit track:%s --set flag-forced=1 ";
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
public List<FileAttribute> loadAttributes(File file) {
|
public List<FileAttribute> loadAttributes(File file) {
|
||||||
@@ -62,100 +68,126 @@ public class MkvFileProcessor implements FileProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug(fileAttributes);
|
log.debug(fileAttributes.toString());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
log.error("File could not be found or loaded: ", e);
|
||||||
log.error("File could not be found or loaded!");
|
System.out.println("File could not be found or loaded: " + file.getAbsolutePath());
|
||||||
}
|
}
|
||||||
return fileAttributes;
|
return fileAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public FileInfoDto filterAttributes(List<FileAttribute> attributes) {
|
public void detectDefaultTracks(FileInfoDto info, List<FileAttribute> attributes, List<FileAttribute> nonForcedTracks) {
|
||||||
FileInfoDto info = new FileInfoDto();
|
|
||||||
List<FileAttribute> nonForcedTracks = attributes.stream()
|
|
||||||
.filter(elem -> !StringUtils.containsAnyIgnoreCase(elem.getTrackName(),
|
|
||||||
Config.getInstance().getForcedKeywords().toArray(new CharSequence[0])))
|
|
||||||
.filter(elem -> !elem.isForcedTrack())
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
List<FileAttribute> nonCommentaryTracks = attributes.stream()
|
|
||||||
.filter(elem -> !StringUtils.containsAnyIgnoreCase(elem.getTrackName(),
|
|
||||||
Config.getInstance().getCommentaryKeywords().toArray(new CharSequence[0])))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
detectDefaultTracks(info, attributes, nonForcedTracks);
|
|
||||||
detectDesiredTracks(info, nonForcedTracks, nonCommentaryTracks);
|
|
||||||
log.debug(info);
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void detectDefaultTracks(FileInfoDto info, List<FileAttribute> attributes, List<FileAttribute> nonForcedTracks) {
|
|
||||||
Set<FileAttribute> detectedForcedSubtitleLanes = new HashSet<>();
|
|
||||||
for (FileAttribute attribute : attributes) {
|
for (FileAttribute attribute : attributes) {
|
||||||
if (attribute.isDefaultTrack() && AUDIO.equals(attribute.getType()))
|
if (AUDIO.equals(attribute.getType())) {
|
||||||
info.getDefaultAudioLanes().add(attribute);
|
if (attribute.isDefaultTrack()) info.getExistingDefaultAudioLanes().add(attribute);
|
||||||
if (attribute.isDefaultTrack() && SUBTITLES.equals(attribute.getType()))
|
if (attribute.isForcedTrack()) info.getExistingForcedAudioLanes().add(attribute);
|
||||||
info.getDefaultSubtitleLanes().add(attribute);
|
} else if (SUBTITLES.equals(attribute.getType())) {
|
||||||
if (attribute.isForcedTrack() && SUBTITLES.equals(attribute.getType()))
|
if (attribute.isDefaultTrack()) info.getExistingDefaultSubtitleLanes().add(attribute);
|
||||||
detectedForcedSubtitleLanes.add(attribute);
|
|
||||||
|
if (attribute.isForcedTrack()) info.getExistingForcedSubtitleLanes().add(attribute);
|
||||||
|
else if (!nonForcedTracks.contains(attribute)) info.getDesiredForcedSubtitleLanes().add(attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info.setDesiredForcedSubtitleLanes(attributes.stream()
|
/**
|
||||||
.filter(e -> !nonForcedTracks.contains(e))
|
* {@inheritDoc}
|
||||||
.filter(e -> !detectedForcedSubtitleLanes.contains(e))
|
*/
|
||||||
.collect(Collectors.toSet())
|
@Override
|
||||||
);
|
public void detectDesiredTracks(FileInfoDto info, List<FileAttribute> nonForcedTracks, List<FileAttribute> nonCommentaryTracks,
|
||||||
}
|
AttributeConfig... configs) {
|
||||||
|
Set<FileAttribute> tracks = SetUtils.retainOf(nonForcedTracks, nonCommentaryTracks);
|
||||||
|
Set<FileAttribute> audioTracks = tracks.stream().filter(a -> AUDIO.equals(a.getType())).collect(Collectors.toSet());
|
||||||
|
Set<FileAttribute> subtitleTracks = tracks.stream().filter(a -> SUBTITLES.equals(a.getType())).collect(Collectors.toSet());
|
||||||
|
|
||||||
protected void detectDesiredTracks(FileInfoDto info, List<FileAttribute> nonForcedTracks, List<FileAttribute> nonCommentaryTracks) {
|
for (AttributeConfig config : configs) {
|
||||||
for (AttributeConfig config : Config.getInstance().getAttributeConfig()) {
|
Optional<FileAttribute> desiredAudio = detectDesiredTrack(config.getAudioLanguage(), audioTracks).findFirst();
|
||||||
FileAttribute desiredAudio = null;
|
Optional<FileAttribute> desiredSubtitle = detectDesiredSubtitleTrack(config.getSubtitleLanguage(), subtitleTracks).findFirst();
|
||||||
FileAttribute desiredSubtitle = null;
|
|
||||||
for (FileAttribute attribute : SetUtils.retainOf(nonForcedTracks, nonCommentaryTracks)) {
|
if (desiredAudio.isPresent() && ("OFF".equals(config.getSubtitleLanguage()) || desiredSubtitle.isPresent())) {
|
||||||
if (attribute.getLanguage().equals(config.getAudioLanguage())
|
info.setMatchedConfig(config);
|
||||||
&& AUDIO.equals(attribute.getType())) desiredAudio = attribute;
|
info.setDesiredDefaultAudioLane(desiredAudio.get());
|
||||||
if (attribute.getLanguage().equals(config.getSubtitleLanguage())
|
info.setDesiredDefaultSubtitleLane(desiredSubtitle.orElse(null));
|
||||||
&& SUBTITLES.equals(attribute.getType())) desiredSubtitle = attribute;
|
|
||||||
}
|
|
||||||
if (desiredAudio != null && desiredSubtitle != null) {
|
|
||||||
info.setDesiredAudioLane(desiredAudio);
|
|
||||||
info.setDesiredSubtitleLane(desiredSubtitle);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Stream<FileAttribute> detectDesiredTrack(String language, Set<FileAttribute> tracks) {
|
||||||
|
return tracks.stream().filter(track -> language.equals(track.getLanguage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Stream<FileAttribute> detectDesiredSubtitleTrack(String language, Set<FileAttribute> tracks) {
|
||||||
|
return detectDesiredTrack(language, tracks)
|
||||||
|
.sorted(subtitleTrackComparator.reversed());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(File file, FileInfoDto fileInfo) throws IOException, RuntimeException {
|
public List<FileAttribute> retrieveNonForcedTracks(List<FileAttribute> attributes) {
|
||||||
|
return attributes.stream()
|
||||||
|
.filter(elem -> !StringUtils.containsAnyIgnoreCase(elem.getTrackName(),
|
||||||
|
Config.getInstance().getForcedKeywords().toArray(new CharSequence[0])))
|
||||||
|
.filter(elem -> !elem.isForcedTrack())
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<FileAttribute> retrieveNonCommentaryTracks(List<FileAttribute> attributes) {
|
||||||
|
return attributes.stream()
|
||||||
|
.filter(elem -> !StringUtils.containsAnyIgnoreCase(elem.getTrackName(),
|
||||||
|
Config.getInstance().getCommentaryKeywords().toArray(new CharSequence[0])))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update(File file, FileInfoDto fileInfo) throws IOException, MkvToolNixException {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(format("\"%s\" ", Config.getInstance().getPathFor(MkvToolNix.MKV_PROP_EDIT)));
|
sb.append(format("\"%s\" ", Config.getInstance().getPathFor(MkvToolNix.MKV_PROP_EDIT)));
|
||||||
sb.append(format("\"%s\" ", file.getAbsolutePath()));
|
sb.append(format("\"%s\" ", file.getAbsolutePath()));
|
||||||
|
|
||||||
if (fileInfo.isAudioDifferent()) {
|
if (fileInfo.isAudioDifferent()) {
|
||||||
if (fileInfo.getDefaultAudioLanes() != null && !fileInfo.getDefaultSubtitleLanes().isEmpty()) {
|
if (fileInfo.getExistingDefaultAudioLanes() != null && !fileInfo.getExistingDefaultAudioLanes().isEmpty()) {
|
||||||
for (FileAttribute track: fileInfo.getDefaultAudioLanes()) {
|
for (FileAttribute track: fileInfo.getExistingDefaultAudioLanes()) {
|
||||||
sb.append(format(DISABLE_DEFAULT_TRACK, track.getId()));
|
sb.append(format(DISABLE_DEFAULT_TRACK, track.getId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.append(format(ENABLE_DEFAULT_TRACK, fileInfo.getDesiredAudioLane().getId()));
|
sb.append(format(ENABLE_DEFAULT_TRACK, fileInfo.getDesiredDefaultAudioLane().getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!fileInfo.getExistingForcedAudioLanes().isEmpty()) {
|
||||||
|
for (FileAttribute track: fileInfo.getExistingForcedAudioLanes()) {
|
||||||
|
sb.append(format(DISABLE_FORCED_TRACK, track.getId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fileInfo.isSubtitleDifferent()) {
|
if (fileInfo.isSubtitleDifferent()) {
|
||||||
if (fileInfo.getDefaultSubtitleLanes() != null && !fileInfo.getDefaultSubtitleLanes().isEmpty()) {
|
if (fileInfo.getExistingDefaultSubtitleLanes() != null && !fileInfo.getExistingDefaultSubtitleLanes().isEmpty()) {
|
||||||
for (FileAttribute track: fileInfo.getDefaultSubtitleLanes()) {
|
for (FileAttribute track: fileInfo.getExistingDefaultSubtitleLanes()) {
|
||||||
sb.append(format(DISABLE_DEFAULT_TRACK, track.getId()));
|
sb.append(format(DISABLE_DEFAULT_TRACK, track.getId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.append(format(ENABLE_DEFAULT_TRACK, fileInfo.getDesiredSubtitleLane().getId()));
|
if (fileInfo.getDesiredDefaultSubtitleLane() != null) {
|
||||||
|
sb.append(format(ENABLE_DEFAULT_TRACK, fileInfo.getDesiredDefaultSubtitleLane().getId()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fileInfo.areForcedTracksDifferent()) {
|
if (fileInfo.areForcedTracksDifferent()) {
|
||||||
for (FileAttribute attribute : fileInfo.getDesiredForcedSubtitleLanes()) {
|
for (FileAttribute attribute : fileInfo.getDesiredForcedSubtitleLanes()) {
|
||||||
sb.append(format(ENABLE_FORCED_TRACK, attribute.getId()));
|
sb.append(format(ENABLE_FORCED_TRACK, attribute.getId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info(sb.toString());
|
||||||
InputStream inputstream = Runtime.getRuntime().exec(sb.toString()).getInputStream();
|
InputStream inputstream = Runtime.getRuntime().exec(sb.toString()).getInputStream();
|
||||||
String output = IOUtils.toString(new InputStreamReader(inputstream));
|
String output = IOUtils.toString(new InputStreamReader(inputstream));
|
||||||
log.debug(output);
|
log.debug(output);
|
||||||
if (output.contains("Error")) throw new RuntimeException(output);
|
if (output.contains("Error")) throw new MkvToolNixException(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SubtitleTrackComparator implements Comparator<FileAttribute> {
|
||||||
|
private final String[] preferredSubtitles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int compare(FileAttribute track1, FileAttribute track2) {
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
if (StringUtils.containsAnyIgnoreCase(track1.getTrackName(), preferredSubtitles)) {
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
if (StringUtils.containsAnyIgnoreCase(track2.getTrackName(), preferredSubtitles)) {
|
||||||
|
result--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == 0) {
|
||||||
|
if (track1.isDefaultTrack()) result++;
|
||||||
|
if (track2.isDefaultTrack()) result--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.kernel;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.exceptions.MkvToolNixException;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.FileCollector;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.FileProcessor;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfoDto;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ResultStatistic;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.DateUtils;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.ProjectUtil;
|
||||||
|
import at.pcgamingfreaks.yaml.YAML;
|
||||||
|
import at.pcgamingfreaks.yaml.YamlInvalidContentException;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.tongfei.progressbar.ProgressBar;
|
||||||
|
import me.tongfei.progressbar.ProgressBarBuilder;
|
||||||
|
import me.tongfei.progressbar.ProgressBarStyle;
|
||||||
|
import net.harawata.appdirs.AppDirsFactory;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public abstract class AttributeUpdaterKernel {
|
||||||
|
|
||||||
|
protected final FileCollector collector;
|
||||||
|
protected final FileProcessor processor;
|
||||||
|
protected final ResultStatistic statistic = ResultStatistic.getInstance();
|
||||||
|
private final ExecutorService executor = Executors.newFixedThreadPool(Config.getInstance().getThreads());
|
||||||
|
|
||||||
|
protected ProgressBarBuilder pbBuilder() {
|
||||||
|
return new ProgressBarBuilder()
|
||||||
|
.setStyle(ProgressBarStyle.ASCII)
|
||||||
|
.setUpdateIntervalMillis(250)
|
||||||
|
.setMaxRenderedLength(75);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public void execute() {
|
||||||
|
statistic.startTimer();
|
||||||
|
|
||||||
|
try (ProgressBar progressBar = pbBuilder().build()) {
|
||||||
|
List<File> files = loadFiles(Config.getInstance().getLibraryPath().getAbsolutePath());
|
||||||
|
progressBar.maxHint(files.size());
|
||||||
|
|
||||||
|
files.forEach(file -> executor.submit(() -> {
|
||||||
|
process(file);
|
||||||
|
progressBar.step();
|
||||||
|
}));
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
executor.awaitTermination(1, TimeUnit.DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
endProcess();
|
||||||
|
|
||||||
|
statistic.stopTimer();
|
||||||
|
statistic.printResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<File> loadExcludedFiles() {
|
||||||
|
List<File> excludedFiles = Config.getInstance().getExcludedDirectories().stream()
|
||||||
|
.map(collector::loadFiles)
|
||||||
|
.flatMap(Collection::stream)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
statistic.increaseTotalBy(excludedFiles.size());
|
||||||
|
statistic.increaseExcludedBy(excludedFiles.size());
|
||||||
|
return excludedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load files or directories to update.
|
||||||
|
* Remove excluded directories.
|
||||||
|
*
|
||||||
|
* @param path Path to library
|
||||||
|
* @return List of files to update.
|
||||||
|
*/
|
||||||
|
abstract List<File> loadFiles(String path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start of the file updating process.
|
||||||
|
* This method is called by the executor and its contents are executed in parallel.
|
||||||
|
*
|
||||||
|
* @param file file or directory to update
|
||||||
|
*/
|
||||||
|
void process(File file) {
|
||||||
|
FileInfoDto fileInfo = new FileInfoDto(file);
|
||||||
|
List<FileAttribute> attributes = processor.loadAttributes(file);
|
||||||
|
|
||||||
|
List<FileAttribute> nonForcedTracks = processor.retrieveNonForcedTracks(attributes);
|
||||||
|
List<FileAttribute> nonCommentaryTracks = processor.retrieveNonCommentaryTracks(attributes);
|
||||||
|
|
||||||
|
processor.detectDefaultTracks(fileInfo, attributes, nonForcedTracks);
|
||||||
|
processor.detectDesiredTracks(fileInfo, nonForcedTracks, nonCommentaryTracks,
|
||||||
|
Config.getInstance().getAttributeConfig().toArray(new AttributeConfig[]{}));
|
||||||
|
|
||||||
|
updateFile(fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist file changes.
|
||||||
|
*
|
||||||
|
* @param fileInfoDto contains information about file and desired configuration.
|
||||||
|
*/
|
||||||
|
protected void updateFile(FileInfoDto fileInfoDto) {
|
||||||
|
statistic.total();
|
||||||
|
switch (fileInfoDto.getStatus()) {
|
||||||
|
case CHANGE_NECESSARY:
|
||||||
|
statistic.shouldChange();
|
||||||
|
commitChange(fileInfoDto);
|
||||||
|
break;
|
||||||
|
case UNABLE_TO_APPLY:
|
||||||
|
statistic.noSuitableConfigFound();
|
||||||
|
break;
|
||||||
|
case ALREADY_SUITED:
|
||||||
|
statistic.alreadyFits();
|
||||||
|
break;
|
||||||
|
case UNKNOWN:
|
||||||
|
default:
|
||||||
|
statistic.failure();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void commitChange(FileInfoDto fileInfo) {
|
||||||
|
if (Config.getInstance().isSafeMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
processor.update(fileInfo.getFile(), fileInfo);
|
||||||
|
statistic.success();
|
||||||
|
log.info("Updated {}", fileInfo.getFile().getAbsolutePath());
|
||||||
|
} catch (IOException | MkvToolNixException e) {
|
||||||
|
statistic.failedChanging();
|
||||||
|
log.warn("File couldn't be updated: '{}', Error: {}", fileInfo.getFile().getAbsoluteFile(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void endProcess() {
|
||||||
|
if (Config.getInstance().isSafeMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String filePath = AppDirsFactory.getInstance().getUserConfigDir(ProjectUtil.getProjectName(), null, null);
|
||||||
|
|
||||||
|
File configDir = Path.of(filePath).toFile();
|
||||||
|
if (!configDir.exists()) configDir.mkdirs();
|
||||||
|
|
||||||
|
File lastExecutionFile = Path.of(filePath + "/last-execution.yml").toFile();
|
||||||
|
if (!lastExecutionFile.exists()) lastExecutionFile.createNewFile();
|
||||||
|
|
||||||
|
YAML yaml = new YAML(lastExecutionFile);
|
||||||
|
yaml.set(Config.getInstance().getNormalizedLibraryPath(), DateUtils.convert(new Date()));
|
||||||
|
yaml.save(lastExecutionFile);
|
||||||
|
} catch (IOException | YamlInvalidContentException e) {
|
||||||
|
log.error("last-execution.yml could not be created or read.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.kernel;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.FileCollector;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.FileProcessor;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfoDto;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.tongfei.progressbar.ProgressBarBuilder;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CoherentAttributeUpdaterKernel extends AttributeUpdaterKernel {
|
||||||
|
|
||||||
|
public CoherentAttributeUpdaterKernel(FileCollector collector, FileProcessor processor) {
|
||||||
|
super(collector, processor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ProgressBarBuilder pbBuilder() {
|
||||||
|
return super.pbBuilder()
|
||||||
|
.setUnit(" directories", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
List<File> loadFiles(String path) {
|
||||||
|
return loadFiles(path, Config.getInstance().getCoherent());
|
||||||
|
}
|
||||||
|
|
||||||
|
List<File> loadFiles(String path, int depth) {
|
||||||
|
List<File> excludedFiles = loadExcludedFiles();
|
||||||
|
List<File> directories = collector.loadDirectories(path, depth)
|
||||||
|
.stream().filter(file -> !excludedFiles.contains(file))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return directories.stream()
|
||||||
|
.filter(dir -> isParentDirectory(dir, directories))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isParentDirectory(File directory, List<File> directories) {
|
||||||
|
String path = directory.getAbsolutePath();
|
||||||
|
return directories.stream()
|
||||||
|
.noneMatch(dir -> dir.getAbsolutePath().contains(path) && !StringUtils.equals(path, dir.getAbsolutePath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update files in directory, if possible, with the same {@link AttributeConfig}.
|
||||||
|
* If {@link Config#isForceCoherent()} then there will be no changes to the file if they don't match the same config.
|
||||||
|
* Otherwise, the default behaviour is executed.
|
||||||
|
* This method is called by the executor and is run in parallel.
|
||||||
|
*
|
||||||
|
* @param file directory containing files
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
void process(File file) {
|
||||||
|
process(file, Config.getInstance().getCoherent());
|
||||||
|
}
|
||||||
|
|
||||||
|
void process(File file, int depth) {
|
||||||
|
// TODO: Implement level crawl if coherence is not possible on user entered depth
|
||||||
|
// IMPL idea: recursive method call, cache needs to be implemented
|
||||||
|
List<FileInfoDto> fileInfos = collector.loadFiles(file.getAbsolutePath()).stream()
|
||||||
|
.map(FileInfoDto::new)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
for (AttributeConfig config : Config.getInstance().getAttributeConfig()) {
|
||||||
|
|
||||||
|
for (FileInfoDto fileInfo : fileInfos) {
|
||||||
|
List<FileAttribute> attributes = processor.loadAttributes(fileInfo.getFile());
|
||||||
|
|
||||||
|
List<FileAttribute> nonForcedTracks = processor.retrieveNonForcedTracks(attributes);
|
||||||
|
List<FileAttribute> nonCommentaryTracks = processor.retrieveNonCommentaryTracks(attributes);
|
||||||
|
|
||||||
|
processor.detectDefaultTracks(fileInfo, attributes, nonForcedTracks);
|
||||||
|
processor.detectDesiredTracks(fileInfo, nonForcedTracks, nonCommentaryTracks, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileInfos.stream().allMatch(elem -> ("OFF".equals(config.getSubtitleLanguage()) || elem.getDesiredDefaultSubtitleLane() != null)
|
||||||
|
&& elem.getDesiredDefaultAudioLane() != null)) {
|
||||||
|
log.info("Found {}/{} match for {}", config.getAudioLanguage(), config.getSubtitleLanguage(), file.getAbsolutePath());
|
||||||
|
fileInfos.forEach(this::updateFile);
|
||||||
|
return; // match found, end process here
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfos.forEach(f -> {
|
||||||
|
f.setDesiredDefaultAudioLane(null);
|
||||||
|
f.setDesiredDefaultSubtitleLane(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("No coherent match found for {}", file.getAbsoluteFile());
|
||||||
|
|
||||||
|
for (FileInfoDto fileInfo : fileInfos) {
|
||||||
|
if (!Config.getInstance().isForceCoherent()) {
|
||||||
|
super.process(fileInfo.getFile());
|
||||||
|
} else {
|
||||||
|
statistic.excluded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.kernel;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.FileCollector;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.FileProcessor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.tongfei.progressbar.ProgressBarBuilder;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class DefaultAttributeUpdaterKernel extends AttributeUpdaterKernel {
|
||||||
|
|
||||||
|
public DefaultAttributeUpdaterKernel(FileCollector collector, FileProcessor processor) {
|
||||||
|
super(collector, processor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ProgressBarBuilder pbBuilder() {
|
||||||
|
return super.pbBuilder()
|
||||||
|
.setUnit(" files", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
List<File> loadFiles(String path) {
|
||||||
|
List<File> excludedFiles = loadExcludedFiles();
|
||||||
|
return collector.loadFiles(Config.getInstance().getLibraryPath().getAbsolutePath()).stream()
|
||||||
|
.filter(file -> !excludedFiles.contains(file))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,11 +2,11 @@ package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
|||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@Log4j2
|
@Slf4j
|
||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class AttributeConfig {
|
public class AttributeConfig {
|
||||||
|
|||||||
@@ -10,22 +10,41 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum ConfigProperty {
|
public enum ConfigProperty {
|
||||||
LIBRARY("library", "Path to library", "l", 1),
|
LIBRARY("library-path", "Path to library", "l", 1),
|
||||||
ATTRIBUTE_CONFIG("attribute-config", "Attribute config to decide which tracks to choose when", "a", Option.UNLIMITED_VALUES),
|
ATTRIBUTE_CONFIG("attribute-config", "Attribute config to decide which tracks to choose when", "a", Option.UNLIMITED_VALUES),
|
||||||
CONFIG_PATH("config-path", "Path to config file", "p", 1),
|
CONFIG_PATH("config-path", "Path to config file", "p", 1),
|
||||||
MKV_TOOL_NIX("mkvtoolnix", "Path to mkv tool nix installation", "m", 1),
|
MKV_TOOL_NIX("mkvtoolnix", "Path to mkv tool nix installation", "m", 1),
|
||||||
SAFE_MODE("safe-mode", "Test run (no files will be changes)", "s", 0),
|
SAFE_MODE("safe-mode", "Test run (no files will be changes)", "s", 0),
|
||||||
COHERENT("coherent", "Try to match whole series with same config", "c", 0),
|
COHERENT("coherent", "Try to match all files in dir of depth with the same config", "c", 1),
|
||||||
|
FORCE_COHERENT("force-coherent", "Force coherent and don't update anything if config fits not whole config (default: false)", "cf", 0),
|
||||||
WINDOWS("windows", "Is operating system windows", null, 0),
|
WINDOWS("windows", "Is operating system windows", null, 0),
|
||||||
|
ONLY_NEW_FILES("only-new-files", "Sets filter-date to last successful execution (Overwrites input of filter-date)", "n", 0),
|
||||||
|
FILTER_DATE("filter-date", "Only consider files created newer than entered date (format: \"dd.MM.yyyy-HH:mm:ss\")", "d", 1),
|
||||||
THREADS("threads", "Thread count (default: 2)", "t", 1),
|
THREADS("threads", "Thread count (default: 2)", "t", 1),
|
||||||
INCLUDE_PATTERN("include-pattern", "Include files matching pattern (default: \".*\")", "i", 1),
|
INCLUDE_PATTERN("include-pattern", "Include files matching pattern (default: \".*\")", "i", 1),
|
||||||
EXCLUDE_DIRECTORY("exclude-directories", "Directories to be excluded, combines with config file", "e", 1),
|
EXCLUDED_DIRECTORY("excluded-directories", "Directories to be excluded, combines with config file", "e", Option.UNLIMITED_VALUES),
|
||||||
FORCED_KEYWORDS("forcedKeywords", "Additional keywords to identify forced tracks", "fk", Option.UNLIMITED_VALUES),
|
FORCED_KEYWORDS("forced-keywords", "Additional keywords to identify forced tracks", "fk", Option.UNLIMITED_VALUES),
|
||||||
COMMENTARY_KEYWORDS("commentary-keywords", "Additional keywords to identify commentary tracks", "ck", Option.UNLIMITED_VALUES),
|
COMMENTARY_KEYWORDS("commentary-keywords", "Additional keywords to identify commentary tracks", "ck", Option.UNLIMITED_VALUES),
|
||||||
|
PREFERRED_SUBTITLES("preferred-subtitles", "Additional keywords to prefer specific subtitle tracks", "ps", Option.UNLIMITED_VALUES),
|
||||||
ARGUMENTS("arguments", "List of arguments", null, 0),
|
ARGUMENTS("arguments", "List of arguments", null, 0),
|
||||||
VERSION("version", "Display version", "v", 0),
|
VERSION("version", "Display version", "v", 0),
|
||||||
HELP("help", "\"For help this is\" - Yoda", "h", 0);
|
HELP("help", "\"For help this is\" - Yoda", "h", 0);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Verify at startup that there are no duplicated shortParameters.
|
||||||
|
*/
|
||||||
|
static {
|
||||||
|
Set<String> shortParameters = new HashSet<>();
|
||||||
|
for (String param : Arrays.stream(ConfigProperty.values()).map(ConfigProperty::abrv).collect(Collectors.toList())) {
|
||||||
|
if (shortParameters.contains(param)) {
|
||||||
|
throw new IllegalStateException("It is not allowed to have multiple properties with the same abbreviation!");
|
||||||
|
}
|
||||||
|
if (param != null) {
|
||||||
|
shortParameters.add(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final String property;
|
private final String property;
|
||||||
private final String description;
|
private final String description;
|
||||||
private final String shortParameter;
|
private final String shortParameter;
|
||||||
@@ -46,19 +65,4 @@ public enum ConfigProperty {
|
|||||||
public int args() {
|
public int args() {
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Verify at startup that there are no duplicated shortParameters.
|
|
||||||
*/
|
|
||||||
static {
|
|
||||||
Set<String> shortParameters = new HashSet<>();
|
|
||||||
for (String param: Arrays.stream(ConfigProperty.values()).map(ConfigProperty::abrv).collect(Collectors.toList())) {
|
|
||||||
if (shortParameters.contains(param)) {
|
|
||||||
throw new IllegalStateException("It is not allowed to have multiple properties with the same abbreviation!");
|
|
||||||
}
|
|
||||||
if (param != null) {
|
|
||||||
shortParameters.add(param);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
|||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Log4j2
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class FileAttribute {
|
public class FileAttribute {
|
||||||
@@ -15,6 +17,24 @@ public class FileAttribute {
|
|||||||
private final boolean forcedTrack;
|
private final boolean forcedTrack;
|
||||||
private final LaneType type;
|
private final LaneType type;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
FileAttribute attribute = (FileAttribute) o;
|
||||||
|
return id == attribute.id
|
||||||
|
&& defaultTrack == attribute.defaultTrack
|
||||||
|
&& forcedTrack == attribute.forcedTrack
|
||||||
|
&& Objects.equals(language, attribute.language)
|
||||||
|
&& Objects.equals(trackName, attribute.trackName)
|
||||||
|
&& type == attribute.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(id, language, trackName, defaultTrack, forcedTrack, type);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "[" + "id=" + id +
|
return "[" + "id=" + id +
|
||||||
|
|||||||
@@ -1,53 +1,79 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class FileInfoDto {
|
public class FileInfoDto {
|
||||||
private Set<FileAttribute> defaultAudioLanes = new HashSet<>();
|
private final File file;
|
||||||
private Set<FileAttribute> defaultSubtitleLanes = new HashSet<>();
|
|
||||||
private Set<FileAttribute> desiredForcedSubtitleLanes;
|
|
||||||
private FileAttribute desiredAudioLane;
|
|
||||||
private FileAttribute desiredSubtitleLane;
|
|
||||||
|
|
||||||
public boolean isUnableToApplyConfig() {
|
private Set<FileAttribute> existingDefaultAudioLanes = new HashSet<>();
|
||||||
return desiredAudioLane == null && desiredSubtitleLane == null;
|
private Set<FileAttribute> existingForcedAudioLanes = new HashSet<>();
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAlreadySuitable() {
|
private Set<FileAttribute> existingDefaultSubtitleLanes = new HashSet<>();
|
||||||
return defaultAudioLanes.contains(desiredAudioLane) && defaultSubtitleLanes.contains(desiredSubtitleLane);
|
private Set<FileAttribute> existingForcedSubtitleLanes = new HashSet<>();
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isChangeNecessary() {
|
private Set<FileAttribute> desiredForcedSubtitleLanes = new HashSet<>();
|
||||||
return isAudioDifferent() || isSubtitleDifferent() || areForcedTracksDifferent();
|
private FileAttribute desiredDefaultAudioLane;
|
||||||
}
|
private FileAttribute desiredDefaultSubtitleLane;
|
||||||
|
private AttributeConfig matchedConfig;
|
||||||
|
|
||||||
public boolean isAudioDifferent() {
|
public boolean isAudioDifferent() {
|
||||||
return desiredAudioLane != null &&
|
return desiredDefaultAudioLane != null &&
|
||||||
(defaultAudioLanes == null || !defaultAudioLanes.contains(desiredAudioLane));
|
(existingDefaultAudioLanes == null || !existingDefaultAudioLanes.contains(desiredDefaultAudioLane) || existingDefaultAudioLanes.size() > 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSubtitleDifferent() {
|
public boolean isSubtitleDifferent() {
|
||||||
return desiredSubtitleLane != null &&
|
return isSubtitleMatchDifferent() || isSubtitleOFF();
|
||||||
(defaultSubtitleLanes == null || !defaultSubtitleLanes.contains(desiredSubtitleLane));
|
}
|
||||||
|
|
||||||
|
private boolean isSubtitleMatchDifferent() {
|
||||||
|
return desiredDefaultSubtitleLane != null
|
||||||
|
&& (existingDefaultSubtitleLanes == null || !existingDefaultSubtitleLanes.contains(desiredDefaultSubtitleLane) || existingDefaultSubtitleLanes.size() > 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSubtitleOFF() {
|
||||||
|
return desiredDefaultSubtitleLane == null && "OFF".equals(matchedConfig.getSubtitleLanguage()) &&
|
||||||
|
(existingDefaultSubtitleLanes != null && !existingDefaultSubtitleLanes.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean areForcedTracksDifferent() {
|
public boolean areForcedTracksDifferent() {
|
||||||
return desiredForcedSubtitleLanes.size() > 0;
|
return !desiredForcedSubtitleLanes.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileStatus getStatus() {
|
||||||
|
if (isChangeNecessary()) return FileStatus.CHANGE_NECESSARY;
|
||||||
|
if (isUnableToApplyConfig()) return FileStatus.UNABLE_TO_APPLY;
|
||||||
|
if (isAlreadySuitable()) return FileStatus.ALREADY_SUITED;
|
||||||
|
return FileStatus.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isUnableToApplyConfig() {
|
||||||
|
return desiredDefaultAudioLane == null && desiredDefaultSubtitleLane == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAlreadySuitable() {
|
||||||
|
return existingDefaultAudioLanes.contains(desiredDefaultAudioLane) && existingDefaultSubtitleLanes.contains(desiredDefaultSubtitleLane);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isChangeNecessary() {
|
||||||
|
return isAudioDifferent() || isSubtitleDifferent() || areForcedTracksDifferent() || !existingForcedAudioLanes.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "[" + "defaultAudioLanes=" + defaultAudioLanes +
|
return "[" + "defaultAudioLanes=" + existingDefaultAudioLanes +
|
||||||
", defaultSubtitleLanes=" + defaultSubtitleLanes +
|
", defaultSubtitleLanes=" + existingDefaultSubtitleLanes +
|
||||||
", desiredForcedSubtitleLanes=" + desiredForcedSubtitleLanes +
|
", desiredForcedSubtitleLanes=" + desiredForcedSubtitleLanes +
|
||||||
", desiredAudioLane=" + desiredAudioLane +
|
", desiredAudioLane=" + desiredDefaultAudioLane +
|
||||||
", desiredSubtitleLane=" + desiredSubtitleLane +
|
", desiredSubtitleLane=" + desiredDefaultSubtitleLane +
|
||||||
']';
|
']';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||||
|
|
||||||
|
public enum FileStatus {
|
||||||
|
CHANGE_NECESSARY,
|
||||||
|
UNABLE_TO_APPLY,
|
||||||
|
ALREADY_SUITED,
|
||||||
|
UNKNOWN;
|
||||||
|
}
|
||||||
@@ -2,10 +2,13 @@ package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
|||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@Slf4j
|
||||||
public class ResultStatistic {
|
public class ResultStatistic {
|
||||||
private static final String result = "Total files: %s%n" +
|
private static final String result = "Total files: %s%n" +
|
||||||
|
"├─ Excluded: %s%n" +
|
||||||
"├─ Should change: %s%n" +
|
"├─ Should change: %s%n" +
|
||||||
"│ ├─ Failed changing: %s%n" +
|
"│ ├─ Failed changing: %s%n" +
|
||||||
"│ └─ Successfully changed: %s%n" +
|
"│ └─ Successfully changed: %s%n" +
|
||||||
@@ -13,8 +16,9 @@ public class ResultStatistic {
|
|||||||
"├─ Already fit config: %s%n" +
|
"├─ Already fit config: %s%n" +
|
||||||
"└─ Failed: %s%n" +
|
"└─ Failed: %s%n" +
|
||||||
"Runtime: %s";
|
"Runtime: %s";
|
||||||
|
private static ResultStatistic instance;
|
||||||
private int filesTotal = 0;
|
private int filesTotal = 0;
|
||||||
|
private int excluded = 0;
|
||||||
|
|
||||||
private int shouldChange = 0;
|
private int shouldChange = 0;
|
||||||
private int failedChanging = 0;
|
private int failedChanging = 0;
|
||||||
@@ -28,10 +32,29 @@ public class ResultStatistic {
|
|||||||
private long startTime = 0;
|
private long startTime = 0;
|
||||||
private long runtime = 0;
|
private long runtime = 0;
|
||||||
|
|
||||||
|
public static ResultStatistic getInstance() {
|
||||||
|
if (instance == null) {
|
||||||
|
instance = new ResultStatistic();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void increaseTotalBy(int amount) {
|
||||||
|
filesTotal += amount;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void total() {
|
public synchronized void total() {
|
||||||
filesTotal++;
|
filesTotal++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void increaseExcludedBy(int amount) {
|
||||||
|
excluded += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void excluded() {
|
||||||
|
excluded++;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void shouldChange() {
|
public synchronized void shouldChange() {
|
||||||
shouldChange++;
|
shouldChange++;
|
||||||
}
|
}
|
||||||
@@ -64,6 +87,11 @@ public class ResultStatistic {
|
|||||||
runtime = System.currentTimeMillis() - startTime;
|
runtime = System.currentTimeMillis() - startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void printResult() {
|
||||||
|
System.out.println(prettyPrint());
|
||||||
|
log.info(this.toString());
|
||||||
|
}
|
||||||
|
|
||||||
private String formatTimer() {
|
private String formatTimer() {
|
||||||
int seconds = (int) (runtime / 1000);
|
int seconds = (int) (runtime / 1000);
|
||||||
int minutes = seconds / 60;
|
int minutes = seconds / 60;
|
||||||
@@ -75,15 +103,27 @@ public class ResultStatistic {
|
|||||||
} else if (hours >= 1) {
|
} else if (hours >= 1) {
|
||||||
return String.format("%sh %sm %ss", hours, minutes % 60, seconds % 60);
|
return String.format("%sh %sm %ss", hours, minutes % 60, seconds % 60);
|
||||||
} else if (minutes >= 1) {
|
} else if (minutes >= 1) {
|
||||||
return String.format("%sm %ss", minutes , seconds % 60);
|
return String.format("%sm %ss", minutes, seconds % 60);
|
||||||
} else {
|
} else {
|
||||||
return String.format("%ss", seconds % 60);
|
return String.format("%ss", seconds % 60);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public String prettyPrint() {
|
||||||
public String toString() {
|
return String.format(result, filesTotal, excluded, shouldChange, failedChanging, successfullyChanged,
|
||||||
return String.format(result, filesTotal, shouldChange, failedChanging, successfullyChanged,
|
|
||||||
noSuitableConfigFound, alreadyFits, failed, formatTimer());
|
noSuitableConfigFound, alreadyFits, failed, formatTimer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ResultStatistic: " + "filesTotal=" + filesTotal +
|
||||||
|
", excluded=" + excluded +
|
||||||
|
", shouldChange=" + shouldChange +
|
||||||
|
" (failedChanging=" + failedChanging +
|
||||||
|
", successfullyChanged=" + successfullyChanged +
|
||||||
|
"), noSuitableConfigFound=" + noSuitableConfigFound +
|
||||||
|
", alreadyFits=" + alreadyFits +
|
||||||
|
", failed=" + failed +
|
||||||
|
", runtime=" + formatTimer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
public class DateUtils {
|
||||||
|
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy-HH:mm:ss");
|
||||||
|
|
||||||
|
public static Date convert(long millis) {
|
||||||
|
return new Date(millis);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert String to date.
|
||||||
|
* @return parsed date, defaultDate if exception occurs
|
||||||
|
*/
|
||||||
|
public static Date convert(String date, Date defaultDate) {
|
||||||
|
try {
|
||||||
|
return dateFormat.parse(date);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
return defaultDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String convert(Date date) {
|
||||||
|
return dateFormat.format(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,10 @@ public class LanguageValidatorUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isAudioLanguageValid(String language) {
|
||||||
|
return !language.equals("OFF") && ISO3_LANGUAGES.contains(language);
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean isLanguageValid(String language) {
|
public static boolean isLanguageValid(String language) {
|
||||||
return ISO3_LANGUAGES.contains(language);
|
return ISO3_LANGUAGES.contains(language);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class ProjectUtil {
|
||||||
|
private static final Properties PROJECT_PROPERTIES = new Properties();
|
||||||
|
|
||||||
|
static {
|
||||||
|
try (InputStream propertiesStream = ProjectUtil.class.getClassLoader().getResourceAsStream("project.properties")) {
|
||||||
|
PROJECT_PROPERTIES.load(propertiesStream);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getVersion() {
|
||||||
|
return PROJECT_PROPERTIES.getProperty("version");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getProjectName() {
|
||||||
|
return PROJECT_PROPERTIES.getProperty("project_name");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
public class VersionUtil {
|
|
||||||
public static String getVersion() {
|
|
||||||
try (InputStream propertiesStream = VersionUtil.class.getClassLoader().getResourceAsStream("version.properties")) {
|
|
||||||
Properties properties = new Properties();
|
|
||||||
properties.load(propertiesStream);
|
|
||||||
|
|
||||||
return properties.getProperty("version");
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.PathUtils.TEST_FILE;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class ConfigLoaderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void initConfig() {
|
||||||
|
String[] sut = new String[]{"-a", "ger:ger", "-l", TEST_FILE,
|
||||||
|
"-s", "-cf", "-n",
|
||||||
|
"-c", "2",
|
||||||
|
"-t", "4",
|
||||||
|
"-i", ".*[abc].*",
|
||||||
|
"-fk", "testForced",
|
||||||
|
"-ck", "testCommentary",
|
||||||
|
"-ps", "testPreferred"
|
||||||
|
};
|
||||||
|
ConfigLoader.initConfig(sut);
|
||||||
|
|
||||||
|
assertTrue(Config.getInstance().getLibraryPath().exists());
|
||||||
|
assertEquals(List.of(new AttributeConfig("ger", "ger")), Config.getInstance().getAttributeConfig());
|
||||||
|
|
||||||
|
assertTrue(Config.getInstance().isSafeMode());
|
||||||
|
assertTrue(Config.getInstance().isForceCoherent());
|
||||||
|
assertTrue(Config.getInstance().isOnlyNewFiles());
|
||||||
|
assertNotNull(Config.getInstance().getFilterDate());
|
||||||
|
|
||||||
|
assertEquals(2, Config.getInstance().getCoherent());
|
||||||
|
assertEquals(4, Config.getInstance().getThreads());
|
||||||
|
assertEquals(".*[abc].*", Config.getInstance().getIncludePattern().pattern());
|
||||||
|
assertTrue(Config.getInstance().getForcedKeywords().contains("testForced"));
|
||||||
|
assertTrue(Config.getInstance().getCommentaryKeywords().contains("testCommentary"));
|
||||||
|
assertTrue(Config.getInstance().getPreferredSubtitles().contains("testPreferred"));
|
||||||
|
|
||||||
|
assertNull(Config.getInstance().getConfigPath());
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,13 +20,12 @@ import static at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ValidationResult.
|
|||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ValidationResult.INVALID;
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ValidationResult.INVALID;
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty.LIBRARY;
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty.LIBRARY;
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.CommandLineOptionsUtil.optionOf;
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.CommandLineOptionsUtil.optionOf;
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.PathUtils.TEST_DIR;
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.PathUtils.TEST_FILE;
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.TestUtil.argumentsOf;
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.TestUtil.argumentsOf;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
class PathValidatorTest {
|
class PathValidatorTest {
|
||||||
private static final String TEST_DIR = "src/test/resources/test-dir";
|
|
||||||
private static final String TEST_FILE = "src/test/resources/test-dir/test-file.mkv";
|
|
||||||
|
|
||||||
private static CommandLineParser parser;
|
private static CommandLineParser parser;
|
||||||
private static Options options;
|
private static Options options;
|
||||||
|
|
||||||
@@ -39,7 +38,7 @@ class PathValidatorTest {
|
|||||||
|
|
||||||
private static Stream<Arguments> provideTestCases() {
|
private static Stream<Arguments> provideTestCases() {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
argumentsOf(LIBRARY, false, null, "library: " + TEST_DIR, new String[]{}, VALID),
|
argumentsOf(LIBRARY, false, null, "library-path: " + TEST_DIR, new String[]{}, VALID),
|
||||||
argumentsOf(LIBRARY, true, null, "", new String[]{"-l", TEST_FILE}, VALID),
|
argumentsOf(LIBRARY, true, null, "", new String[]{"-l", TEST_FILE}, VALID),
|
||||||
|
|
||||||
argumentsOf(LIBRARY, false, TEST_DIR, "", new String[]{}, DEFAULT),
|
argumentsOf(LIBRARY, false, TEST_DIR, "", new String[]{}, DEFAULT),
|
||||||
@@ -48,7 +47,7 @@ class PathValidatorTest {
|
|||||||
argumentsOf(LIBRARY, false, null, "", new String[]{}, NOT_PRESENT),
|
argumentsOf(LIBRARY, false, null, "", new String[]{}, NOT_PRESENT),
|
||||||
|
|
||||||
argumentsOf(LIBRARY, true, null, "", new String[]{"-l", TEST_DIR + "/invalid"}, INVALID),
|
argumentsOf(LIBRARY, true, null, "", new String[]{"-l", TEST_DIR + "/invalid"}, INVALID),
|
||||||
argumentsOf(LIBRARY, false, null, "library: " + TEST_DIR + "/invalid", new String[]{}, INVALID),
|
argumentsOf(LIBRARY, false, null, "library-path: " + TEST_DIR + "/invalid", new String[]{}, INVALID),
|
||||||
argumentsOf(LIBRARY, true, TEST_DIR, "", new String[]{"-l", TEST_DIR + "/invalid"}, INVALID)
|
argumentsOf(LIBRARY, true, TEST_DIR, "", new String[]{"-l", TEST_DIR + "/invalid"}, INVALID)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class SetValidatorTest {
|
|||||||
return Stream.of(
|
return Stream.of(
|
||||||
argumentsOf(COMMENTARY_KEYWORDS, true, true, "", new String[]{"-ck", "test"}, VALID, 3),
|
argumentsOf(COMMENTARY_KEYWORDS, true, true, "", new String[]{"-ck", "test"}, VALID, 3),
|
||||||
argumentsOf(COMMENTARY_KEYWORDS, true, false, COMMENTARY_KEYWORDS.prop() + ": [test]", new String[]{}, VALID, 1),
|
argumentsOf(COMMENTARY_KEYWORDS, true, false, COMMENTARY_KEYWORDS.prop() + ": [test]", new String[]{}, VALID, 1),
|
||||||
|
argumentsOf(COMMENTARY_KEYWORDS, true, false, COMMENTARY_KEYWORDS.prop() + ":\n - test\n - test2", new String[]{}, VALID, 2),
|
||||||
argumentsOf(COMMENTARY_KEYWORDS, false, true, COMMENTARY_KEYWORDS.prop() + ": [test]", new String[]{}, VALID, 3),
|
argumentsOf(COMMENTARY_KEYWORDS, false, true, COMMENTARY_KEYWORDS.prop() + ": [test]", new String[]{}, VALID, 3),
|
||||||
argumentsOf(COMMENTARY_KEYWORDS, false, false, "", new String[]{"-ck", "test"}, VALID, 1),
|
argumentsOf(COMMENTARY_KEYWORDS, false, false, "", new String[]{"-ck", "test"}, VALID, 1),
|
||||||
argumentsOf(COMMENTARY_KEYWORDS, true, true, COMMENTARY_KEYWORDS.prop() + ": [commentary]", new String[]{}, VALID, 2),
|
argumentsOf(COMMENTARY_KEYWORDS, true, true, COMMENTARY_KEYWORDS.prop() + ": [commentary]", new String[]{}, VALID, 2),
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.LaneType;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class SubtitleTrackComparatorTest {
|
||||||
|
private static final SubtitleTrackComparator comparator = new SubtitleTrackComparator(new String[]{"unstyled"});
|
||||||
|
|
||||||
|
private static Stream<Arguments> compareArguments() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(List.of(attr("unstyled sub", false), attr("styled sub", false)),
|
||||||
|
List.of(attr("unstyled sub", false), attr("styled sub", false))),
|
||||||
|
Arguments.of(List.of(attr("styled sub", false), attr("unstyled sub", false)),
|
||||||
|
List.of(attr("unstyled sub", false), attr("styled sub", false))),
|
||||||
|
|
||||||
|
Arguments.of(List.of(attr("unstyled sub", true), attr("styled sub", false)),
|
||||||
|
List.of(attr("unstyled sub", true), attr("styled sub", false))),
|
||||||
|
Arguments.of(List.of(attr("styled sub", true), attr("unstyled sub", false)),
|
||||||
|
List.of(attr("unstyled sub", false), attr("styled sub", true))),
|
||||||
|
|
||||||
|
Arguments.of(List.of(attr("unstyled sub", true), attr("unstyled sub", false)),
|
||||||
|
List.of(attr("unstyled sub", true), attr("unstyled sub", false)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("compareArguments")
|
||||||
|
void compare(List<FileAttribute> input, List<FileAttribute> expected) {
|
||||||
|
List<FileAttribute> result = input.stream().sorted(comparator.reversed()).collect(Collectors.toList());
|
||||||
|
|
||||||
|
assertArrayEquals(expected.toArray(new FileAttribute[0]), result.toArray(new FileAttribute[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileAttribute attr(String trackName, boolean defaultTrack) {
|
||||||
|
return new FileAttribute(0, "", trackName, defaultTrack, false, LaneType.SUBTITLES);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||||
|
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.TestUtil.createFileInfo;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class FileInfoDtoTest {
|
||||||
|
private static final FileAttribute AUDIO_GER_DEFAULT = new FileAttribute(0, "ger", "", true, false, LaneType.AUDIO);
|
||||||
|
private static final FileAttribute AUDIO_GER = new FileAttribute(0, "ger", "", false, false, LaneType.AUDIO);
|
||||||
|
private static final FileAttribute AUDIO_ENG_DEFAULT = new FileAttribute(1, "eng", "", true, false, LaneType.AUDIO);
|
||||||
|
private static final FileAttribute AUDIO_ENG = new FileAttribute(1, "eng", "", false, false, LaneType.AUDIO);
|
||||||
|
|
||||||
|
private static final FileAttribute SUB_GER_DEFAULT = new FileAttribute(0, "ger", "", true, false, LaneType.SUBTITLES);
|
||||||
|
private static final FileAttribute SUB_GER = new FileAttribute(0, "ger", "", false, false, LaneType.SUBTITLES);
|
||||||
|
private static final FileAttribute SUB_ENG_DEFAULT = new FileAttribute(1, "eng", "", true, false, LaneType.SUBTITLES);
|
||||||
|
private static final FileAttribute SUB_ENG = new FileAttribute(1, "eng", "", false, false, LaneType.SUBTITLES);
|
||||||
|
|
||||||
|
|
||||||
|
private static Stream<Arguments> isAudioDifferent() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(createFileInfo(Set.of(AUDIO_GER_DEFAULT), AUDIO_GER_DEFAULT), false),
|
||||||
|
Arguments.of(createFileInfo(Set.of(AUDIO_GER_DEFAULT), AUDIO_ENG), true),
|
||||||
|
Arguments.of(createFileInfo(Set.of(AUDIO_GER_DEFAULT, AUDIO_ENG_DEFAULT), AUDIO_GER_DEFAULT), true),
|
||||||
|
Arguments.of(createFileInfo(Set.of(), AUDIO_GER), true),
|
||||||
|
Arguments.of(createFileInfo(null, AUDIO_GER), true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
void isAudioDifferent(FileInfoDto underTest, boolean expected) {
|
||||||
|
assertEquals(expected, underTest.isAudioDifferent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> isSubtitleDifferent() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(createFileInfo(Set.of(SUB_GER_DEFAULT), SUB_GER_DEFAULT, new AttributeConfig("", "ger")), false),
|
||||||
|
Arguments.of(createFileInfo(Set.of(SUB_GER_DEFAULT), SUB_ENG, new AttributeConfig("", "eng")), true),
|
||||||
|
Arguments.of(createFileInfo(Set.of(SUB_GER_DEFAULT, SUB_ENG_DEFAULT), SUB_ENG, new AttributeConfig("", "eng")), true),
|
||||||
|
Arguments.of(createFileInfo(Set.of(), SUB_ENG, new AttributeConfig("", "ger")), true),
|
||||||
|
Arguments.of(createFileInfo(null, SUB_GER, new AttributeConfig("", "ger")), true),
|
||||||
|
Arguments.of(createFileInfo(null, null, new AttributeConfig("", "OFF")), false),
|
||||||
|
Arguments.of(createFileInfo(Set.of(), null, new AttributeConfig("", "OFF")), false),
|
||||||
|
Arguments.of(createFileInfo(Set.of(SUB_GER_DEFAULT), null, new AttributeConfig("", "OFF")), true)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
void isSubtitleDifferent(FileInfoDto underTest, boolean expected) {
|
||||||
|
assertEquals(expected, underTest.isSubtitleDifferent());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class DateUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void convert() {
|
||||||
|
Date expectedDate = new Date(0);
|
||||||
|
String expectedString = "01.01.1970-01:00:00";
|
||||||
|
|
||||||
|
assertEquals(expectedDate, DateUtils.convert(0));
|
||||||
|
assertEquals(expectedDate, DateUtils.convert(expectedString, expectedDate));
|
||||||
|
assertEquals(expectedDate, DateUtils.convert("1234;15", expectedDate));
|
||||||
|
assertEquals(expectedString, DateUtils.convert(expectedDate));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
public class PathUtils {
|
||||||
|
public static final String TEST_DIR = "src/test/resources/test-dir";
|
||||||
|
public static final String TEST_FILE = "src/test/resources/test-dir/test-file.mkv";
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class SetUtilsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void retainOf() {
|
||||||
|
List<Integer> list1 = List.of(1, 2, 3, 4, 5);
|
||||||
|
List<Integer> list2 = List.of(2, 4, 6, 8, 10);
|
||||||
|
Set<Integer> expected = Set.of(2, 4);
|
||||||
|
|
||||||
|
assertEquals(expected, SetUtils.retainOf(list1, list2));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ValidationResult;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ValidationResult;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfoDto;
|
||||||
import org.junit.jupiter.params.provider.Arguments;
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static java.util.stream.Collectors.joining;
|
import static java.util.stream.Collectors.joining;
|
||||||
|
|
||||||
@@ -25,4 +29,18 @@ public class TestUtil {
|
|||||||
return Arguments.of(property, required, append, yaml, cmd, result, expectedSize);
|
return Arguments.of(property, required, append, yaml, cmd, result, expectedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static FileInfoDto createFileInfo(Set<FileAttribute> defaultAudio, FileAttribute desiredAudio) {
|
||||||
|
FileInfoDto fileInfoDto = new FileInfoDto(null);
|
||||||
|
fileInfoDto.setExistingDefaultAudioLanes(defaultAudio);
|
||||||
|
fileInfoDto.setDesiredDefaultAudioLane(desiredAudio);
|
||||||
|
return fileInfoDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FileInfoDto createFileInfo(Set<FileAttribute> defaultSubtitle, FileAttribute desiredSubtitle, AttributeConfig config) {
|
||||||
|
FileInfoDto fileInfoDto = new FileInfoDto(null);
|
||||||
|
fileInfoDto.setExistingDefaultSubtitleLanes(defaultSubtitle);
|
||||||
|
fileInfoDto.setDesiredDefaultSubtitleLane(desiredSubtitle);
|
||||||
|
fileInfoDto.setMatchedConfig(config);
|
||||||
|
return fileInfoDto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
version=${project.version}
|
|
||||||
Reference in New Issue
Block a user