mirror of
https://github.com/RatzzFatzz/MKVAudioSubtitleChanger.git
synced 2026-02-11 02:05:56 +01:00
Compare commits
149 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bdd3874e7 | ||
|
|
3e74e23512 | ||
|
|
5f2248653b | ||
|
|
15128583df | ||
|
|
76d25fca1b | ||
|
|
957295127a | ||
|
|
15177268a0 | ||
|
|
fa572030da | ||
|
|
cc23d8c5bc | ||
|
|
94a3b419e0 | ||
|
|
f88fcd0bd5 | ||
|
|
69a70eb66f | ||
|
|
e5e5f56aed | ||
|
|
b6b15faf7d | ||
|
|
ca29c22f00 | ||
|
|
1ae5b1bef1 | ||
|
|
cf04e14de2 | ||
|
|
2ecea906b1 | ||
|
|
d3248e646b | ||
|
|
a51922968e | ||
|
|
80c46508b8 | ||
|
|
a5b24e907d | ||
|
|
7427e3aa27 | ||
|
|
d7ae865d55 | ||
|
|
363492be43 | ||
|
|
37cedecea7 | ||
|
|
04722d9279 | ||
|
|
0b61deccbf | ||
|
|
d5e452557c | ||
|
|
e7a13c9f1d | ||
|
|
63bcd92db9 | ||
|
|
0b8dfa7464 | ||
|
|
f08a6ef1da | ||
|
|
ae541e6fdf | ||
|
|
aa5fd26b32 | ||
|
|
181c718e7a | ||
|
|
5eca28ecb9 | ||
|
|
9ab417f71d | ||
|
|
0f6bc271b1 | ||
|
|
99f929aabb | ||
|
|
a156db16fe | ||
|
|
b0f927dfa8 | ||
|
|
37c65df60c | ||
| 0e9d008c7e | |||
| 3205969d3b | |||
| d24aedb0af | |||
| b86c7b98a5 | |||
|
|
69c192c08b | ||
|
|
7dd01234b6 | ||
|
|
8f38abcf3a | ||
|
|
fc4e80ead0 | ||
|
|
e81b06f6fa | ||
|
|
dc770c9325 | ||
|
|
471255a09b | ||
|
|
9c8315aec7 | ||
|
|
c33777b038 | ||
|
|
6c08ce69ea | ||
|
|
7f8c14e3a9 | ||
|
|
553c672e4d | ||
|
|
d98c4cd49e | ||
|
|
21f244ff3f | ||
|
|
ffac36ac27 | ||
|
|
0813744148 | ||
|
|
44d2601d3e | ||
|
|
36bd93bb50 | ||
|
|
ecc5c56c8c | ||
|
|
f6310c71ee | ||
|
|
bb4a686dfc | ||
|
|
c63fcd4f37 | ||
|
|
9f15b542bd | ||
|
|
76321bb904 | ||
|
|
895597b91f | ||
|
|
4fa5448e1c | ||
|
|
f3accd77d6 | ||
|
|
2710ea2602 | ||
|
|
547b5ad86c | ||
|
|
1863432dc6 | ||
|
|
7ea0ab17b0 | ||
|
|
47b4cdc896 | ||
|
|
b638d93358 | ||
|
|
939f6053dd | ||
|
|
4714ef8db1 | ||
|
|
321115b9ca | ||
|
|
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 | |||
|
|
9f40d97d8a | ||
| 156e327943 | |||
|
|
5f72f4545f | ||
|
|
1e341a0112 | ||
| fe30d186df | |||
| ce9a2fc805 | |||
| f69fbedee0 | |||
| d0c4b07f52 | |||
| 313abd311a | |||
| 47f6d65eb2 | |||
| 33276b7aa2 | |||
| 51b4885e65 | |||
|
|
847a3f1f68 | ||
|
|
937c644b32 | ||
| 1d6098efc1 | |||
| b5030f9401 | |||
| 892bc59803 | |||
| 1bd136af6a | |||
| d8f0dcdc87 | |||
| 10cfb07457 | |||
|
|
17158e3f15 | ||
| 5e27a72499 | |||
| 658849417a | |||
| 8d63c02abd | |||
| bad2a39614 | |||
|
|
2d861b0f1c |
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
github: [RatzzFatzz]
|
||||||
|
custom: "https://paypal.me/ratzmichael"
|
||||||
144
.github/workflows/release.yml
vendored
Normal file
144
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
# This workflow will run every time a tag starting with v is created.
|
||||||
|
|
||||||
|
name: Build and release
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [ created ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
portable-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install mkvtoolnix
|
||||||
|
run: sudo apt-get install -y mkvtoolnix
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
|
- name: Extract version from tag
|
||||||
|
id: get_version
|
||||||
|
run: echo "VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d'-' -f1)" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set timezone
|
||||||
|
uses: szenius/set-timezone@v2.0
|
||||||
|
with:
|
||||||
|
timezoneLinux: "Europe/Berlin"
|
||||||
|
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4.7.0
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: 17
|
||||||
|
|
||||||
|
- name: Setup workspace
|
||||||
|
run: mkdir artifacts
|
||||||
|
|
||||||
|
- name: Build with Maven
|
||||||
|
run: |
|
||||||
|
mvn clean package --file pom.xml -P portable -Drevision="${{ steps.get_version.outputs.VERSION }}"
|
||||||
|
cp target/M*.{zip,tar} artifacts/
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: skx/github-action-publish-binaries@master
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
args: 'artifacts/M*'
|
||||||
|
|
||||||
|
debian-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install mkvtoolnix
|
||||||
|
run: sudo apt-get install -y mkvtoolnix
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
|
- name: Extract version from tag
|
||||||
|
id: get_version
|
||||||
|
run: echo "VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d'-' -f1)" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set timezone
|
||||||
|
uses: szenius/set-timezone@v2.0
|
||||||
|
with:
|
||||||
|
timezoneLinux: "Europe/Berlin"
|
||||||
|
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v4.7.0
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: 17
|
||||||
|
|
||||||
|
- name: Setup workspace
|
||||||
|
run: mkdir artifacts
|
||||||
|
|
||||||
|
- name: Build with Maven
|
||||||
|
run: |
|
||||||
|
mvn clean package --file pom.xml -P debian -Drevision="${{ steps.get_version.outputs.VERSION }}"
|
||||||
|
cp target/M*.deb artifacts/
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: skx/github-action-publish-binaries@master
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
args: 'artifacts/M*'
|
||||||
|
|
||||||
|
windows-installer-build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Install mkvtoolnix
|
||||||
|
uses: crazy-max/ghaction-chocolatey@v3
|
||||||
|
with:
|
||||||
|
args: install mkvtoolnix -y
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
|
- name: Extract version from tag
|
||||||
|
id: get_version
|
||||||
|
run: echo "VERSION=$(echo ${GITHUB_REF_NAME#v} | cut -d'-' -f1)" >> $GITHUB_OUTPUT
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Set timezone
|
||||||
|
uses: szenius/set-timezone@v2.0
|
||||||
|
with:
|
||||||
|
timezoneWindows: "Berlin Standard Time"
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4.7.0
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: 21
|
||||||
|
|
||||||
|
- name: Setup workspace
|
||||||
|
run: mkdir artifacts
|
||||||
|
|
||||||
|
- name: Build with Maven
|
||||||
|
run: mvn clean package --file pom.xml -P windows -Drevision="${{ steps.get_version.outputs.VERSION }}"
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: AButler/upload-release-assets@v3.0
|
||||||
|
with:
|
||||||
|
files: 'target/installer/*'
|
||||||
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
# Log file
|
# Log file
|
||||||
*.log
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
# BlueJ files
|
# BlueJ files
|
||||||
*.ctxt
|
*.ctxt
|
||||||
|
|||||||
126
README.md
126
README.md
@@ -1,59 +1,83 @@
|
|||||||
### Table of content
|
## Introduction
|
||||||
- Introduction
|
|
||||||
- Requirements
|
|
||||||
- Running
|
|
||||||
- Configuration
|
|
||||||
- Additional parameters
|
|
||||||
|
|
||||||
### Introduction
|
|
||||||
|
|
||||||
This program helps to change audio and subtitle lines of mkv files.
|
A streamlined solution for managing MKV files, this program leverages MKVToolNix to modify audio and subtitle track properties without the need for time-consuming file reencoding. Users can easily set their track preferences, and the application intelligently applies the best matching configuration. The tool focuses on metadata modification rather than full file rewriting, ensuring quick operations while maintaining the original file integrity. This makes it an ideal choice for managing multilingual media collections or batch processing multiple MKV files.
|
||||||
|
|
||||||
### Requirements
|

|
||||||
|
|
||||||
- Java 11 or higher
|
## Requirements
|
||||||
|
|
||||||
|
- Java 21 or newer
|
||||||
- mkvtoolnix installation
|
- mkvtoolnix installation
|
||||||
|
|
||||||
### Running
|
## Execution
|
||||||
|
Portable:
|
||||||
1. Extract downloaded archive
|
```
|
||||||
2. Copy `config-template.yaml` to `config.yaml`
|
java -jar mkvaudiosubtitlechanger-<version>.jar --library "X:/Files" --attribute-config eng:ger eng:OFF
|
||||||
3. Update `config.yaml` to fit your needs
|
```
|
||||||
4. Open terminal / cmd in the directory of the jar and the config file
|
Windows & Linux (installed):
|
||||||
5. Execute following commands:
|
```
|
||||||
1. (Optional) `java -jar mkvaudiosubtitleschanger.jar -l [path to mkv or dir with mkv] --safe-mode`
|
mkvaudiosubtitlechanger --library "X:/Files" --attribute-config eng:ger eng:OFF
|
||||||
2. To permanently apply changes: `java -jar mkvaudiosubtitleschanger.jar -l [path to mkv or dir with mkv]`
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
|
|
||||||
Config file needs to be placed in the same directory as the jar or path to config has to be passed via command line
|
|
||||||
argument.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
Add `--safe-mode` oder `-s` to not change any files. This is recommended for the first executions.
|
||||||
|
|
||||||
### Additional arameters
|
Attribute-config must be entered in pairs: `audio:subtitle`; Example: `jpn:eng`. More about this topic
|
||||||
These properties overwrite already existing values in the config file.
|
[here](https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/wiki/Attribute-Config).
|
||||||
```properties
|
|
||||||
-c,--config <arg> Path to config file
|
### Available parameters
|
||||||
-e,--exclude-directories <arg> Directories to be excluded, combines with config file
|
```
|
||||||
-h,--help "for help this is" - Yoda
|
* -a, --attribute-config=<attributeConfig>...
|
||||||
-i,--include-pattern <arg> Include files matching pattern
|
List of audio:subtitle pairs used to match in order
|
||||||
-k,--forcedKeywords <arg> Additional keywords to identify forced tracks, combines with config file
|
and update files accordingly (e.g. jpn:eng jpn:
|
||||||
-l,--library <arg> Path to library
|
ger)
|
||||||
-m,--mkvtoolnix <arg> Path to mkv tool nix installation
|
* -l, --library=<libraryPath>
|
||||||
-s,--safe-mode Test run (no files will be changes)
|
path to library
|
||||||
-t,--threads <arg> thread count (default: 2)
|
-m, --mkvtoolnix=<mkvToolNix>
|
||||||
-v,--version Display version
|
path to mkvtoolnix installation
|
||||||
```
|
-s, --safemode test run (no files will be changes)
|
||||||
|
-t, --threads=<threads> thread count (default: 2)
|
||||||
|
-c, --coherent=<coherent> try to match all files in dir of depth with the
|
||||||
|
same attribute config
|
||||||
|
-cf, --force-coherent changes are only applied if it's a coherent match
|
||||||
|
-n, --only-new-file sets filter-date to last successful execution
|
||||||
|
(overwrites input of filter-date)
|
||||||
|
-d, --filter-date=<filterDate>
|
||||||
|
only consider files created newer than entered date
|
||||||
|
(format: "dd.MM.yyyy-HH:mm:ss")
|
||||||
|
-i, --include-pattern=<includePattern>
|
||||||
|
include files matching pattern (default: ".*")
|
||||||
|
-e, --excluded=<excluded>...
|
||||||
|
Directories and files to be excluded (no wildcard)
|
||||||
|
-o, -overwrite-forced remove all forced flags
|
||||||
|
--forced-keywords=<forcedKeywords>[, <forcedKeywords>...]...
|
||||||
|
Keywords to identify forced tracks (Defaults will
|
||||||
|
be overwritten; Default: forced, signs, songs)
|
||||||
|
--commentary-keywords=<commentaryKeywords>[, <commentaryKeywords>...]...
|
||||||
|
Keywords to identify commentary tracks (Defaults
|
||||||
|
will be overwritten; Default: comment,
|
||||||
|
commentary, director)
|
||||||
|
--hearing-impaired=<hearingImpaired>[, <hearingImpaired>...]...
|
||||||
|
Keywords to identify hearing impaired tracks
|
||||||
|
(Defaults will be overwritten; Default: SDH
|
||||||
|
--preferred-subtitles=<preferredSubtitles>[, <preferredSubtitles>...]...
|
||||||
|
Keywords to prefer specific subtitle tracks
|
||||||
|
(Defaults will be overwritten; Default: unstyled)
|
||||||
|
--debug Enable debug logging
|
||||||
|
-h, --help Show this help message and exit.
|
||||||
|
-V, --version Print version information and exit.
|
||||||
|
```
|
||||||
|
If you need more information how each parameter works, check out [this wiki page](https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/wiki/Parameters-v4).
|
||||||
|
|
||||||
|
All parameters can also be defined in a [config file](https://picocli.info/#_argument_files_for_long_command_lines).
|
||||||
|
|
||||||
|
## Build requirements
|
||||||
|
- JDK 21 or newer
|
||||||
|
- Maven 3
|
||||||
|
- Git
|
||||||
|
|
||||||
|
## Build from source
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/RatzzFatzz/MKVAudioSubtitleChanger.git
|
||||||
|
cd MKVAudioSubtitleChanger
|
||||||
|
mvn clean package -Pportable
|
||||||
|
```
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
mkvtoolnix: C:\Program Files\MKVToolNix
|
|
||||||
|
|
||||||
config:
|
|
||||||
1:
|
|
||||||
audio: ger
|
|
||||||
subtitle: OFF
|
|
||||||
2:
|
|
||||||
audio: eng
|
|
||||||
subtitle: ger
|
|
||||||
|
|
||||||
# Recommendations for data stored on HDDs, increase when using SSDs
|
|
||||||
#threads: 2
|
|
||||||
#forcedKeywords: ["forced", "signs"]
|
|
||||||
#exclude-directories:
|
|
||||||
# - "D:/Path/To/File.mkv"
|
|
||||||
# - "D:/Path/To/Directory"
|
|
||||||
#include-pattern: "regex"
|
|
||||||
|
|
||||||
BIN
example.gif
Normal file
BIN
example.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@@ -14,10 +14,6 @@
|
|||||||
<destName>${project.artifactId}.jar</destName>
|
<destName>${project.artifactId}.jar</destName>
|
||||||
<outputDirectory>/</outputDirectory>
|
<outputDirectory>/</outputDirectory>
|
||||||
</file>
|
</file>
|
||||||
<file>
|
|
||||||
<source>${project.basedir}/config-template.yaml</source>
|
|
||||||
<outputDirectory>/</outputDirectory>
|
|
||||||
</file>
|
|
||||||
<file>
|
<file>
|
||||||
<source></source>
|
<source></source>
|
||||||
</file>
|
</file>
|
||||||
|
|||||||
405
pom.xml
405
pom.xml
@@ -4,23 +4,243 @@
|
|||||||
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>2.0</version>
|
<version>${revision}</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<revision>1.0.0-SNAPSHOT</revision>
|
||||||
|
<mainClass>at.pcgamingfreaks.mkvaudiosubtitlechanger.Main</mainClass>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
||||||
|
<project.maintainer>RatzzFatzz</project.maintainer>
|
||||||
|
<project.maintainer.mail>github.contact@ratzloeffel.de</project.maintainer.mail>
|
||||||
|
<project.description>Command-line utility for batch-managing default audio and subtitle tracks in MKV files.</project.description>
|
||||||
|
|
||||||
|
<java-version>17</java-version>
|
||||||
|
<lombok-version>1.18.42</lombok-version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>portable</id>
|
||||||
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>log4j2.yaml</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
|
<version>3.3.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>archive</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>single</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<appendAssemblyId>false</appendAssemblyId>
|
||||||
|
<descriptors>
|
||||||
|
<descriptor>maven/assembly.xml</descriptor>
|
||||||
|
</descriptors>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>windows</id>
|
||||||
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>log4j2-windows.yaml</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.4.2</version>
|
||||||
|
<configuration>
|
||||||
|
<finalName>${project.artifactId}</finalName>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
<version>3.3.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-jpackage-input</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-resources</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/jpackage-input</outputDirectory>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>${project.build.directory}</directory>
|
||||||
|
<includes>
|
||||||
|
<include>${project.artifactId}.jar</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>filter-windows-installer-info</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-resources</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/wix-resources</outputDirectory>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>${project.basedir}/src/wix/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.panteleyev</groupId>
|
||||||
|
<artifactId>jpackage-maven-plugin</artifactId>
|
||||||
|
<version>1.7.1</version>
|
||||||
|
<configuration>
|
||||||
|
<name>${project.artifactId}</name>
|
||||||
|
<vendor>RatzzFatzz</vendor>
|
||||||
|
<appVersion>${project.version}</appVersion>
|
||||||
|
|
||||||
|
<destination>target/installer</destination>
|
||||||
|
|
||||||
|
<input>target/jpackage-input</input>
|
||||||
|
<mainClass>at.pcgamingfreaks.mkvaudiosubtitlechanger.Main</mainClass>
|
||||||
|
<mainJar>${project.artifactId}.jar</mainJar>
|
||||||
|
|
||||||
|
<resourceDir>${project.build.directory}/wix-resources/</resourceDir>
|
||||||
|
<type>EXE</type>
|
||||||
|
<winConsole>true</winConsole>
|
||||||
|
<winShortcut>false</winShortcut>
|
||||||
|
<winMenu>false</winMenu>
|
||||||
|
<javaOptions>
|
||||||
|
<javaOption>-Dlog4j2.configurationFile=log4j2-windows.yaml</javaOption>
|
||||||
|
</javaOptions>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>windows</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>jpackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>debian</id>
|
||||||
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>log4j2-debian.yaml</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
<version>3.3.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>filter-linux-package-info</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-resources</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/debian-package-info</outputDirectory>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>${project.basedir}/src/deb</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>jdeb</artifactId>
|
||||||
|
<groupId>org.vafer</groupId>
|
||||||
|
<version>1.13</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>jdeb</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<dataSet>
|
||||||
|
<!-- JAR file -->
|
||||||
|
<data>
|
||||||
|
<src>${project.build.directory}/${project.build.finalName}.jar</src>
|
||||||
|
<type>file</type>
|
||||||
|
<mapper>
|
||||||
|
<type>perm</type>
|
||||||
|
<prefix>/usr/lib/${project.artifactId}</prefix>
|
||||||
|
</mapper>
|
||||||
|
</data>
|
||||||
|
<!-- Launcher script -->
|
||||||
|
<data>
|
||||||
|
<src>${project.build.directory}/debian-package-info/bin/mkvaudiosubtitlechanger</src>
|
||||||
|
<type>file</type>
|
||||||
|
<mapper>
|
||||||
|
<type>perm</type>
|
||||||
|
<prefix>/usr/bin</prefix>
|
||||||
|
<filemode>755</filemode>
|
||||||
|
</mapper>
|
||||||
|
</data>
|
||||||
|
</dataSet>
|
||||||
|
<controlDir>${project.build.directory}/debian-package-info/control</controlDir>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
<defaultGoal>clean package</defaultGoal>
|
<defaultGoal>clean package</defaultGoal>
|
||||||
<sourceDirectory>src/main/java</sourceDirectory>
|
<sourceDirectory>src/main/java</sourceDirectory>
|
||||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||||
<resources>
|
<resources>
|
||||||
<resource>
|
|
||||||
<directory>src/main/resources</directory>
|
|
||||||
</resource>
|
|
||||||
<resource>
|
<resource>
|
||||||
<directory>./</directory>
|
<directory>./</directory>
|
||||||
<includes>
|
<includes>
|
||||||
<include>language-codes</include>
|
<include>language-codes</include>
|
||||||
<include>version.properties</include>
|
<include>project.properties</include>
|
||||||
|
<include>LICENSE</include>
|
||||||
</includes>
|
</includes>
|
||||||
<filtering>true</filtering>
|
<filtering>true</filtering>
|
||||||
</resource>
|
</resource>
|
||||||
@@ -35,11 +255,11 @@
|
|||||||
<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.4.2</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<archive>
|
<archive>
|
||||||
<manifestEntries>
|
<manifestEntries>
|
||||||
<Main-Class>at/pcgamingfreaks/mkvaudiosubtitlechanger/Main</Main-Class>
|
<Main-Class>${mainClass}</Main-Class>
|
||||||
</manifestEntries>
|
</manifestEntries>
|
||||||
</archive>
|
</archive>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -47,7 +267,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.6.0</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
@@ -62,6 +282,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>
|
||||||
@@ -69,35 +296,33 @@
|
|||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>2.22.2</version>
|
<version>3.5.2</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.13.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>11</source>
|
<source>${java-version}</source>
|
||||||
<target>11</target>
|
<target>${java-version}</target>
|
||||||
|
<compilerArgs>
|
||||||
|
<arg>-Aproject=${project.groupId}/${project.artifactId}</arg>
|
||||||
|
</compilerArgs>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok-version}</version>
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<groupId>info.picocli</groupId>
|
||||||
|
<artifactId>picocli-codegen</artifactId>
|
||||||
|
<version>4.7.7</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
|
||||||
<version>3.3.0</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>single</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<appendAssemblyId>false</appendAssemblyId>
|
|
||||||
<descriptors>
|
|
||||||
<descriptor>maven/assembly.xml</descriptor>
|
|
||||||
</descriptors>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
@@ -109,101 +334,157 @@
|
|||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.intellij/forms_rt -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.intellij</groupId>
|
<groupId>com.intellij</groupId>
|
||||||
<artifactId>forms_rt</artifactId>
|
<artifactId>forms_rt</artifactId>
|
||||||
<version>7.0.3</version>
|
<version>7.0.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>1.18.8</version>
|
<version>${lombok-version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/info.picocli/picocli -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>info.picocli</groupId>
|
||||||
|
<artifactId>picocli</artifactId>
|
||||||
|
<version>4.7.7</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Hibernate Validator -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hibernate.validator</groupId>
|
||||||
|
<artifactId>hibernate-validator</artifactId>
|
||||||
|
<version>8.0.2.Final</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Expression Language Implementation -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/jakarta.el/jakarta.el-api -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.el</groupId>
|
||||||
|
<artifactId>jakarta.el-api</artifactId>
|
||||||
|
<version>6.1.0-M1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish</groupId>
|
||||||
|
<artifactId>jakarta.el</artifactId>
|
||||||
|
<version>5.0.0-M1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jakarta Bean Validation -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.validation</groupId>
|
||||||
|
<artifactId>jakarta.validation-api</artifactId>
|
||||||
|
<version>3.1.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- region logging -->
|
<!-- region logging -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
|
||||||
<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.24.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
|
||||||
<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.24.3</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j18-impl -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j2-impl -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
<artifactId>log4j-slf4j-impl</artifactId>
|
<artifactId>log4j-slf4j2-impl</artifactId>
|
||||||
<version>2.17.1</version>
|
<version>2.24.3</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>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
<artifactId>jackson-dataformat-yaml</artifactId>
|
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||||
<version>2.13.1</version>
|
<version>2.20.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
|
||||||
<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.2.1</version>
|
<version>2.20.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/commons-cli/commons-cli -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-cli</groupId>
|
<groupId>commons-cli</groupId>
|
||||||
<artifactId>commons-cli</artifactId>
|
<artifactId>commons-cli</artifactId>
|
||||||
<version>1.5.0</version>
|
<version>1.10.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>3.12.0</version>
|
<version>3.19.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/me.tongfei/progressbar -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>me.tongfei</groupId>
|
<groupId>me.tongfei</groupId>
|
||||||
<artifactId>progressbar</artifactId>
|
<artifactId>progressbar</artifactId>
|
||||||
<version>0.9.3</version>
|
<version>0.10.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- endregion -->
|
<!-- endregion -->
|
||||||
<!-- region unit-tests -->
|
<!-- region unit-tests -->
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter-api</artifactId>
|
<artifactId>junit-jupiter-api</artifactId>
|
||||||
<version>5.4.2</version>
|
<version>6.0.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.junit.jupiter</groupId>
|
<groupId>org.junit.jupiter</groupId>
|
||||||
<artifactId>junit-jupiter-engine</artifactId>
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
<version>5.4.2</version>
|
<version>6.0.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
|
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.mockito</groupId>
|
<groupId>org.mockito</groupId>
|
||||||
<artifactId>mockito-all</artifactId>
|
<artifactId>mockito-core</artifactId>
|
||||||
<version>1.9.5</version>
|
<version>5.20.0</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-junit-jupiter</artifactId>
|
||||||
|
<version>5.20.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-params -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-params</artifactId>
|
||||||
|
<version>6.0.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- endregion -->
|
<!-- endregion -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>at.pcgamingfreaks</groupId>
|
<groupId>at.pcgamingfreaks</groupId>
|
||||||
<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.5.0</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
src/deb/bin/mkvaudiosubtitlechanger
Normal file
1
src/deb/bin/mkvaudiosubtitlechanger
Normal file
@@ -0,0 +1 @@
|
|||||||
|
java -Dlog4j2.configurationFile=log4j2-debian.yaml -jar /usr/lib/${project.artifactId}/${project.artifactId}-${project.version}.jar "$@"
|
||||||
8
src/deb/control/control
Normal file
8
src/deb/control/control
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
Package: [[artifactId]]
|
||||||
|
Version: [[version]]
|
||||||
|
Section: misc
|
||||||
|
Priority: optional
|
||||||
|
Architecture: all
|
||||||
|
Depends: java-runtime (>=${java-version}), mkvtoolnix
|
||||||
|
Maintainer: ${project.maintainer} <${project.maintainer.mail}>
|
||||||
|
Description: ${project.description}
|
||||||
@@ -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().getThreadCount());
|
|
||||||
private final FileCollector collector;
|
|
||||||
private final FileProcessor processor;
|
|
||||||
private final ResultStatistic statistic = new ResultStatistic();
|
|
||||||
|
|
||||||
public AttributeUpdaterKernel(FileCollector collector, FileProcessor processor) {
|
|
||||||
this.collector = collector;
|
|
||||||
this.processor = processor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public void execute() {
|
|
||||||
statistic.startTimer();
|
|
||||||
|
|
||||||
try (ProgressBar progressBar = pbBuilder().build()) {
|
|
||||||
List<File> excludedFiles = Config.getInstance().getExcludedDirectories().stream()
|
|
||||||
.map(collector::loadFiles)
|
|
||||||
.flatMap(Collection::stream)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
List<File> files = collector.loadFiles(Config.getInstance().getLibraryPath()).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 e) {
|
|
||||||
statistic.failedChanging();
|
|
||||||
log.warn("File couldn't be updated: {}", file.getAbsoluteFile());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,18 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
|
||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.CommandRunner;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.MkvFileCollector;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation.ValidationExecutionStrategy;
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.MkvFileProcessor;
|
import picocli.CommandLine;
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
|
|
||||||
@Log4j2
|
|
||||||
public class Main {
|
public class Main {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
Config.getInstance().initConfig(args);
|
if (args.length == 0) {
|
||||||
AttributeUpdaterKernel kernel = new AttributeUpdaterKernel(new MkvFileCollector(), new MkvFileProcessor());
|
CommandLine.usage(CommandRunner.class, System.out);
|
||||||
kernel.execute();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new CommandLine(CommandRunner.class)
|
||||||
|
.setExecutionStrategy(new ValidationExecutionStrategy())
|
||||||
|
.execute(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,223 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
|
|
||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.MkvToolNix;
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.VersionUtil;
|
|
||||||
import at.pcgamingfreaks.yaml.YAML;
|
|
||||||
import at.pcgamingfreaks.yaml.YamlInvalidContentException;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
import org.apache.commons.cli.*;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.regex.PatternSyntaxException;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty.*;
|
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.CommandLineOptionsUtil.optionOf;
|
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.LanguageValidatorUtil.isLanguageValid;
|
|
||||||
|
|
||||||
@Log4j2
|
|
||||||
@Getter
|
|
||||||
public class Config {
|
|
||||||
@Getter(AccessLevel.NONE)
|
|
||||||
CommandLineParser parser = new DefaultParser();
|
|
||||||
@Getter(AccessLevel.NONE)
|
|
||||||
HelpFormatter formatter = new HelpFormatter();
|
|
||||||
|
|
||||||
@Getter(AccessLevel.NONE)
|
|
||||||
private static Config config = null;
|
|
||||||
|
|
||||||
private File configPath;
|
|
||||||
private String libraryPath;
|
|
||||||
private boolean isSafeMode;
|
|
||||||
|
|
||||||
private int threadCount;
|
|
||||||
private Pattern includePattern;
|
|
||||||
@Getter(AccessLevel.NONE)
|
|
||||||
private String mkvToolNixPath;
|
|
||||||
|
|
||||||
private boolean isWindows;
|
|
||||||
|
|
||||||
private final Set<String> forcedKeywords = new HashSet<>(Arrays.asList("forced", "signs"));
|
|
||||||
private final Set<String> excludedDirectories = new HashSet<>();
|
|
||||||
|
|
||||||
private List<AttributeConfig> attributeConfig;
|
|
||||||
|
|
||||||
public static Config getInstance() {
|
|
||||||
if (config == null) {
|
|
||||||
config = new Config();
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void initConfig(String[] args) throws InvalidConfigException {
|
|
||||||
ConfigErrors errors = new ConfigErrors();
|
|
||||||
CommandLine cmd = null;
|
|
||||||
Options options = initOptions();
|
|
||||||
|
|
||||||
try {
|
|
||||||
cmd = parser.parse(options, args);
|
|
||||||
if (cmd == null) throw new NullPointerException();
|
|
||||||
} catch (ParseException | NullPointerException e) {
|
|
||||||
formatter.printHelp(106, "java -jar MKVAudioSubtitlesChanger.jar -l <path_to_library>",
|
|
||||||
"\nParameters:", options,
|
|
||||||
"\nFeature requests and bug reports: https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/issues");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
exitIfHelp(cmd, options);
|
|
||||||
exitIfVersion(cmd);
|
|
||||||
|
|
||||||
configPath = loadConfigPath(cmd, errors);
|
|
||||||
libraryPath = loadLibraryPath(cmd, errors);
|
|
||||||
isSafeMode = cmd.hasOption(SAFE_MODE.prop());
|
|
||||||
|
|
||||||
try (YAML config = new YAML(configPath)) {
|
|
||||||
threadCount = loadThreadCount(cmd, config);
|
|
||||||
includePattern = loadIncludePattern(cmd, config, errors);
|
|
||||||
mkvToolNixPath = loadMkvToolNixPath(cmd, config, errors);
|
|
||||||
|
|
||||||
isWindows = loadOperatingSystem();
|
|
||||||
|
|
||||||
loadForcedKeywords(cmd, config);
|
|
||||||
loadExcludedDirectories(cmd, config);
|
|
||||||
|
|
||||||
attributeConfig = loadAttributeConfig(config, errors);
|
|
||||||
} catch (IOException | YamlInvalidContentException ignored) {}
|
|
||||||
|
|
||||||
if (errors.hasErrors()) {
|
|
||||||
throw new InvalidConfigException(errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Options initOptions() {
|
|
||||||
Options options = new Options();
|
|
||||||
options.addOption(optionOf(HELP, "h", false));
|
|
||||||
options.addOption(optionOf(VERSION, "v", false));
|
|
||||||
options.addOption(optionOf(LIBRARY, "l", true));
|
|
||||||
options.addOption(optionOf(MKV_TOOL_NIX, "m", true));
|
|
||||||
options.addOption(optionOf(CONFIG_PATH, "c", true));
|
|
||||||
options.addOption(optionOf(THREADS, "t", true));
|
|
||||||
options.addOption(optionOf(SAFE_MODE, "s", false));
|
|
||||||
options.addOption(optionOf(FORCED_KEYWORDS, "k", Option.UNLIMITED_VALUES, false));
|
|
||||||
options.addOption(optionOf(EXCLUDE_DIRECTORY, "e", Option.UNLIMITED_VALUES, false));
|
|
||||||
options.addOption(optionOf(INCLUDE_PATTERN, "i", true));
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void exitIfHelp(CommandLine cmd, Options options) {
|
|
||||||
if (cmd.hasOption("help")) {
|
|
||||||
formatter.printHelp(106, "java -jar MKVAudioSubtitlesChanger.jar -l <path_to_library>",
|
|
||||||
"\nParameters:", options,
|
|
||||||
"\nFeature requests and bug reports: https://github.com/RatzzFatzz/MKVAudioSubtitleChanger/issues");
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void exitIfVersion(CommandLine cmd) {
|
|
||||||
if (cmd.hasOption(VERSION.prop())) {
|
|
||||||
System.out.printf("MKV Audio Subtitle Changer Version %s%n", VersionUtil.getVersion());
|
|
||||||
System.exit(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private File loadConfigPath(CommandLine cmd, ConfigErrors errors) {
|
|
||||||
File configPath = new File(cmd.getOptionValue(CONFIG_PATH.prop(), "config.yaml"));
|
|
||||||
if (configPath.isFile()) return configPath;
|
|
||||||
|
|
||||||
errors.add("invalid config path");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String loadLibraryPath(CommandLine cmd, ConfigErrors errors) {
|
|
||||||
if (cmd.hasOption(LIBRARY.prop())) {
|
|
||||||
File libraryPath = new File(cmd.getOptionValue(LIBRARY.prop()));
|
|
||||||
if (libraryPath.isFile() || libraryPath.isDirectory()) {
|
|
||||||
return libraryPath.getAbsolutePath();
|
|
||||||
} else {
|
|
||||||
errors.add("invalid library path");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errors.add("missing library path");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private int loadThreadCount(CommandLine cmd, YAML config) {
|
|
||||||
return cmd.hasOption(THREADS.prop())
|
|
||||||
? Integer.parseInt(cmd.getOptionValue(THREADS.prop()))
|
|
||||||
: config.getInt(THREADS.prop(), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Pattern loadIncludePattern(CommandLine cmd, YAML config, ConfigErrors errors) {
|
|
||||||
try {
|
|
||||||
return Pattern.compile(cmd.hasOption(INCLUDE_PATTERN.prop())
|
|
||||||
? cmd.getOptionValue(INCLUDE_PATTERN.prop())
|
|
||||||
: config.getString(INCLUDE_PATTERN.prop(), ".*"));
|
|
||||||
} catch (PatternSyntaxException e) {
|
|
||||||
errors.add("invalid regex pattern");
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private String loadMkvToolNixPath(CommandLine cmd, YAML config, ConfigErrors errors){
|
|
||||||
if (cmd.hasOption(MKV_TOOL_NIX.prop())) return cmd.getOptionValue(MKV_TOOL_NIX.prop());
|
|
||||||
if (config.isSet(MKV_TOOL_NIX.prop())) return config.getString(MKV_TOOL_NIX.prop());
|
|
||||||
errors.add("path to mkv tool nix installation missing");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean loadOperatingSystem() {
|
|
||||||
return System.getProperty("os.name").toLowerCase().contains("windows");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private void loadForcedKeywords(CommandLine cmd, YAML config) {
|
|
||||||
if (cmd.hasOption(FORCED_KEYWORDS.prop())) forcedKeywords.addAll(List.of(cmd.getOptionValues(FORCED_KEYWORDS.prop())));
|
|
||||||
if (config.isSet(FORCED_KEYWORDS.prop())) forcedKeywords.addAll(config.getStringList(FORCED_KEYWORDS.prop()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private void loadExcludedDirectories(CommandLine cmd, YAML config) {
|
|
||||||
if (cmd.hasOption(EXCLUDE_DIRECTORY.prop())) excludedDirectories.addAll(List.of(cmd.getOptionValues(EXCLUDE_DIRECTORY.prop())));
|
|
||||||
if (config.isSet(EXCLUDE_DIRECTORY.prop())) excludedDirectories.addAll(config.getStringList(EXCLUDE_DIRECTORY.prop()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<AttributeConfig> loadAttributeConfig(YAML config, ConfigErrors errors) {
|
|
||||||
Function<String, String> audio = key -> config.getString(key + ".audio", null);
|
|
||||||
Function<String, String> subtitle = key -> config.getString(key + ".subtitle", null);
|
|
||||||
|
|
||||||
List<AttributeConfig> attributeConfigs = config.getKeysFiltered(".*audio.*").stream()
|
|
||||||
.sorted()
|
|
||||||
.map(key -> key.replace(".audio", ""))
|
|
||||||
.map(key -> new AttributeConfig(audio.apply(key), subtitle.apply(key)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
if (attributeConfigs.isEmpty()) {
|
|
||||||
errors.add("no language configuration");
|
|
||||||
} else {
|
|
||||||
for (AttributeConfig attributeConfig : attributeConfigs) {
|
|
||||||
isLanguageValid(attributeConfig.getAudioLanguage(), errors);
|
|
||||||
isLanguageValid(attributeConfig.getSubtitleLanguage(), errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return attributeConfigs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPathFor(MkvToolNix exe) {
|
|
||||||
return mkvToolNixPath.endsWith("/") ? mkvToolNixPath + exe : mkvToolNixPath + "/" + exe;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ConfigErrors {
|
|
||||||
private final List<String> errors = new ArrayList<>();
|
|
||||||
|
|
||||||
public void add(String errorMessage) {
|
|
||||||
errors.add(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasErrors() {
|
|
||||||
return !errors.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return StringUtils.capitalize(String.join(", ", errors));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
|
|
||||||
|
|
||||||
public class InvalidConfigException extends RuntimeException{
|
|
||||||
public InvalidConfigException(ConfigErrors errors) {
|
|
||||||
super("Errors in config: " + errors);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,46 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors.*;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.InputConfig;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.ProjectUtil;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.logging.log4j.Level;
|
||||||
|
import org.apache.logging.log4j.core.config.Configurator;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@CommandLine.Command(
|
||||||
|
name = "mkvaudiosubtitlechanger",
|
||||||
|
usageHelpAutoWidth = true,
|
||||||
|
customSynopsis = {
|
||||||
|
"mkvaudiosubtitlechanger [-a <attributeConfig> [...<attributeConfig>]] [-s] <libraryPath>",
|
||||||
|
"Example: mkvaudiosubtitlechanger -a eng:eng eng:ger -s /mnt/media/",
|
||||||
|
""
|
||||||
|
},
|
||||||
|
requiredOptionMarker = '*',
|
||||||
|
sortOptions = false,
|
||||||
|
mixinStandardHelpOptions = true,
|
||||||
|
versionProvider = ProjectUtil.class
|
||||||
|
)
|
||||||
|
public class CommandRunner implements Runnable {
|
||||||
|
@Getter
|
||||||
|
@CommandLine.ArgGroup(exclusive = false)
|
||||||
|
private InputConfig config;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (config.isDebug()) {
|
||||||
|
Configurator.setRootLevel(Level.DEBUG);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileFilter fileFilter = new FileFilter(config.getExcluded(), config.getIncludePattern(), config.getFilterDate());
|
||||||
|
FileProcessor fileProcessor = new CachedFileProcessor(new MkvFileProcessor(config.getMkvToolNix(), fileFilter));
|
||||||
|
AttributeChangeProcessor attributeChangeProcessor = new AttributeChangeProcessor(config.getPreferredSubtitles().toArray(new String[0]), config.getForcedKeywords(), config.getCommentaryKeywords(), config.getHearingImpaired());
|
||||||
|
|
||||||
|
AttributeUpdater kernel = config.getCoherent() != null
|
||||||
|
? new CoherentAttributeUpdater(config, fileProcessor, attributeChangeProcessor)
|
||||||
|
: new SingleFileAttributeUpdater(config, fileProcessor, attributeChangeProcessor);
|
||||||
|
kernel.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface FileCollector {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param path leads to one file directly or a directory which will be loaded recursively
|
|
||||||
* @return list of all files within the directory
|
|
||||||
*/
|
|
||||||
List<File> loadFiles(String path);
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,89 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ResultStatistic;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.DateUtils;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
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;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class FileFilter {
|
public class FileFilter {
|
||||||
static boolean accept(File pathName, String[] fileExtensions) {
|
private final Set<String> excluded;
|
||||||
return StringUtils.endsWithAny(pathName.getAbsolutePath().toLowerCase(), fileExtensions)
|
private final Pattern includePattern;
|
||||||
&& Config.getInstance().getIncludePattern().matcher(pathName.getName()).matches();
|
private final Date filterDate;
|
||||||
|
|
||||||
|
private final String EXTENSION_GROUP = "extension";
|
||||||
|
private final Pattern extensionPattern = Pattern.compile(String.format(".*(?<%s>\\..*)", EXTENSION_GROUP));
|
||||||
|
|
||||||
|
public boolean accept(File pathName, Set<String> fileExtensions) {
|
||||||
|
// Ignore files irrelevant for statistics
|
||||||
|
if (!hasProperFileExtension(pathName, new HashSet<>(fileExtensions))) {
|
||||||
|
log.debug("Ignored {}", pathName);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasMatchingPattern(pathName)
|
||||||
|
|| !isNewer(pathName)
|
||||||
|
|| isExcluded(pathName, new HashSet<>(excluded))) {
|
||||||
|
log.debug("Excluded {}", pathName);
|
||||||
|
ResultStatistic.getInstance().excluded();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasProperFileExtension(File pathName, Set<String> fileExtensions) {
|
||||||
|
Matcher matcher = extensionPattern.matcher(pathName.getName());
|
||||||
|
return matcher.find() && fileExtensions.contains(matcher.group(EXTENSION_GROUP));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasMatchingPattern(File pathName) {
|
||||||
|
return includePattern.matcher(pathName.getName()).matches();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNewer(File pathName) {
|
||||||
|
if (filterDate == 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 boolean isNewer(Date creationDate) {
|
||||||
|
return creationDate.toInstant().isAfter(filterDate.toInstant());
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isExcluded(File pathName, Set<String> excludedDirs) {
|
||||||
|
if (excludedDirs.contains(pathName.getPath())) return true;
|
||||||
|
|
||||||
|
String[] pathSplit = pathName.getPath().split("/");
|
||||||
|
for (String excludedDir : excludedDirs) {
|
||||||
|
String[] excludeSplit = excludedDir.split("/");
|
||||||
|
if (excludeSplit.length > pathSplit.length) continue;
|
||||||
|
boolean matchingPaths = true;
|
||||||
|
for (int i = 0; i < excludeSplit.length; i++) {
|
||||||
|
if (!excludeSplit[i].equals(pathSplit[i])) {
|
||||||
|
matchingPaths = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (matchingPaths) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
|
||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfoDto;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface FileProcessor {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param file Takes the file from which the attributes will be returned
|
|
||||||
* @return list of all important attributes
|
|
||||||
*/
|
|
||||||
List<FileAttribute> loadAttributes(File file);
|
|
||||||
|
|
||||||
FileInfoDto filterAttributes(List<FileAttribute> attributes);
|
|
||||||
|
|
||||||
void update(File file, FileInfoDto fileInfo) throws IOException;
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
|
||||||
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@Log4j2
|
|
||||||
public class MkvFileCollector implements FileCollector {
|
|
||||||
private static final String[] fileExtensions = new String[]{".mkv", ".mka", ".mks", ".mk3d"};
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<File> loadFiles(String path) {
|
|
||||||
try (Stream<Path> paths = Files.walk(Paths.get(path))) {
|
|
||||||
return paths.filter(Files::isRegularFile)
|
|
||||||
.map(Path::toFile)
|
|
||||||
.filter(file -> FileFilter.accept(file, fileExtensions))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Couldn't find file or directory!", e);
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
|
||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.Config;
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.*;
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.apache.logging.log4j.core.util.IOUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.LaneType.AUDIO;
|
|
||||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.model.LaneType.SUBTITLES;
|
|
||||||
import static java.lang.String.format;
|
|
||||||
|
|
||||||
@Log4j2
|
|
||||||
public class MkvFileProcessor implements FileProcessor {
|
|
||||||
private final ObjectMapper mapper = new ObjectMapper();
|
|
||||||
private final String[] forcedKeywords = new String[]{"forced", "signs"};
|
|
||||||
private static final String DISABLE_DEFAULT_TRACK = "--edit track:%s --set flag-default=0 ";
|
|
||||||
private static final String ENABLE_DEFAULT_TRACK = "--edit track:%s --set flag-default=1 ";
|
|
||||||
private static final String ENABLE_FORCED_TRACK = "--edit track:%s --set flag-forced=1 ";
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<FileAttribute> loadAttributes(File file) {
|
|
||||||
Map<String, Object> jsonMap;
|
|
||||||
List<FileAttribute> fileAttributes = new ArrayList<>();
|
|
||||||
try {
|
|
||||||
String command = format("\"%s\"", Config.getInstance().getPathFor(MkvToolNix.MKV_MERGER));
|
|
||||||
String[] arguments = new String[]{
|
|
||||||
command,
|
|
||||||
"--identify",
|
|
||||||
"--identification-format",
|
|
||||||
"json",
|
|
||||||
file.getAbsoluteFile().toString()
|
|
||||||
};
|
|
||||||
|
|
||||||
InputStream inputStream = Runtime.getRuntime().exec(arguments).getInputStream();
|
|
||||||
jsonMap = mapper.readValue(inputStream, Map.class);
|
|
||||||
List<Map<String, Object>> tracks = (List<Map<String, Object>>) jsonMap.get("tracks");
|
|
||||||
if (tracks == null) {
|
|
||||||
log.warn("Couldn't retrieve information of {}", file.getAbsolutePath());
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
for (Map<String, Object> attribute : tracks) {
|
|
||||||
if (!"video".equals(attribute.get("type"))) {
|
|
||||||
Map<String, Object> properties = (Map<String, Object>) attribute.get("properties");
|
|
||||||
fileAttributes.add(new FileAttribute(
|
|
||||||
(int) properties.get("number"),
|
|
||||||
(String) properties.get("language"),
|
|
||||||
(String) properties.get("track_name"),
|
|
||||||
(Boolean) properties.getOrDefault("default_track", false),
|
|
||||||
(Boolean) properties.getOrDefault("forced_track", false),
|
|
||||||
LaneType.valueOf(((String) attribute.get("type")).toUpperCase(Locale.ENGLISH))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug(fileAttributes);
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
log.error("File could not be found or loaded!");
|
|
||||||
}
|
|
||||||
return fileAttributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileInfoDto filterAttributes(List<FileAttribute> attributes) {
|
|
||||||
FileInfoDto info = new FileInfoDto();
|
|
||||||
List<FileAttribute> nonForcedTracks = attributes.stream()
|
|
||||||
.filter(elem -> !StringUtils.containsAnyIgnoreCase(elem.getTrackName(), forcedKeywords))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
detectDefaultTracks(attributes, info, nonForcedTracks);
|
|
||||||
detectDesiredTracks(info, nonForcedTracks);
|
|
||||||
log.debug(info);
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void detectDefaultTracks(List<FileAttribute> attributes, FileInfoDto info, List<FileAttribute> nonForcedTracks) {
|
|
||||||
Set<FileAttribute> detectedForcedSubtitleLanes = new HashSet<>();
|
|
||||||
for (FileAttribute attribute : attributes) {
|
|
||||||
if (attribute.isDefaultTrack() && AUDIO.equals(attribute.getType()))
|
|
||||||
info.getDefaultAudioLanes().add(attribute);
|
|
||||||
if (attribute.isDefaultTrack() && SUBTITLES.equals(attribute.getType()))
|
|
||||||
info.getDefaultSubtitleLanes().add(attribute);
|
|
||||||
if (attribute.isForcedTrack() && SUBTITLES.equals(attribute.getType()))
|
|
||||||
detectedForcedSubtitleLanes.add(attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
info.setDesiredForcedSubtitleLanes(attributes.stream()
|
|
||||||
.filter(e -> !nonForcedTracks.contains(e))
|
|
||||||
.filter(e -> !detectedForcedSubtitleLanes.contains(e))
|
|
||||||
.collect(Collectors.toSet())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void detectDesiredTracks(FileInfoDto info, List<FileAttribute> nonForcedTracks) {
|
|
||||||
for (AttributeConfig config : Config.getInstance().getAttributeConfig()) {
|
|
||||||
FileAttribute desiredAudio = null;
|
|
||||||
FileAttribute desiredSubtitle = null;
|
|
||||||
for (FileAttribute attribute : nonForcedTracks) {
|
|
||||||
if (attribute.getLanguage().equals(config.getAudioLanguage())
|
|
||||||
&& AUDIO.equals(attribute.getType())) desiredAudio = attribute;
|
|
||||||
if (attribute.getLanguage().equals(config.getSubtitleLanguage())
|
|
||||||
&& SUBTITLES.equals(attribute.getType())) desiredSubtitle = attribute;
|
|
||||||
}
|
|
||||||
if (desiredAudio != null && desiredSubtitle != null) {
|
|
||||||
info.setDesiredAudioLane(desiredAudio);
|
|
||||||
info.setDesiredSubtitleLane(desiredSubtitle);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(File file, FileInfoDto fileInfo) throws IOException {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
sb.append(format("\"%s\" ", Config.getInstance().getPathFor(MkvToolNix.MKV_PROP_EDIT)));
|
|
||||||
sb.append(format("\"%s\" ", file.getAbsolutePath()));
|
|
||||||
if (fileInfo.isAudioDifferent()) {
|
|
||||||
if (fileInfo.getDefaultAudioLanes() != null && !fileInfo.getDefaultSubtitleLanes().isEmpty()) {
|
|
||||||
for (FileAttribute track: fileInfo.getDefaultAudioLanes()) {
|
|
||||||
sb.append(format(DISABLE_DEFAULT_TRACK, track.getId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.append(format(ENABLE_DEFAULT_TRACK, fileInfo.getDesiredAudioLane().getId()));
|
|
||||||
}
|
|
||||||
if (fileInfo.isSubtitleDifferent()) {
|
|
||||||
if (fileInfo.getDefaultSubtitleLanes() != null && !fileInfo.getDefaultSubtitleLanes().isEmpty()) {
|
|
||||||
for (FileAttribute track: fileInfo.getDefaultSubtitleLanes()) {
|
|
||||||
sb.append(format(DISABLE_DEFAULT_TRACK, track.getId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sb.append(format(ENABLE_DEFAULT_TRACK, fileInfo.getDesiredSubtitleLane().getId()));
|
|
||||||
}
|
|
||||||
if (fileInfo.areForcedTracksDifferent()) {
|
|
||||||
for (FileAttribute attribute : fileInfo.getDesiredForcedSubtitleLanes()) {
|
|
||||||
sb.append(format(ENABLE_FORCED_TRACK, attribute.getId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream inputstream = Runtime.getRuntime().exec(sb.toString()).getInputStream();
|
|
||||||
log.debug(IOUtils.toString(new InputStreamReader(inputstream)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackAttributes;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SubtitleTrackComparator implements Comparator<TrackAttributes> {
|
||||||
|
private final String[] preferredSubtitles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int compare(TrackAttributes track1, TrackAttributes track2) {
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
if (StringUtils.containsAnyIgnoreCase(track1.trackName(), preferredSubtitles)) {
|
||||||
|
result++;
|
||||||
|
}
|
||||||
|
if (StringUtils.containsAnyIgnoreCase(track2.trackName(), preferredSubtitles)) {
|
||||||
|
result--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == 0) {
|
||||||
|
if (track1.defaultt()) result++;
|
||||||
|
if (track2.defaultt()) result--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.converter;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.LanguageValidatorUtil.isLanguageValid;
|
||||||
|
|
||||||
|
public class AttributeConfigConverter implements CommandLine.ITypeConverter<AttributeConfig> {
|
||||||
|
private static final String AUDIO_GROUP = "audio";
|
||||||
|
private static final String SUB_GROUP = "sub";
|
||||||
|
private static final Pattern PATTERN = Pattern.compile(String.format("^(?<%s>.{3}):(?<%s>.{3})$", AUDIO_GROUP, SUB_GROUP));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the input string into an AttributeConfig object.
|
||||||
|
*
|
||||||
|
* @param s The input string containing audio and subtitle language configuration in format "audioLang:subtitleLang"
|
||||||
|
* @return An AttributeConfig object representing the parsed configuration
|
||||||
|
* @throws CommandLine.TypeConversionException if the input string is invalid or contains invalid language codes
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AttributeConfig convert(String s) {
|
||||||
|
Matcher matcher = PATTERN.matcher(s);
|
||||||
|
|
||||||
|
if (!matcher.find()) throw new CommandLine.TypeConversionException("Invalid Attribute config: " + s);
|
||||||
|
|
||||||
|
return validateResult(new AttributeConfig(matcher.group(AUDIO_GROUP), matcher.group(SUB_GROUP)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that both language codes in the {@link AttributeConfig} object are valid.
|
||||||
|
*
|
||||||
|
* @param attr {@link AttributeConfig} object to validate
|
||||||
|
* @throws CommandLine.TypeConversionException if either language code is invalid
|
||||||
|
* @return valid {@link AttributeConfig}
|
||||||
|
*/
|
||||||
|
private static AttributeConfig validateResult(AttributeConfig attr) {
|
||||||
|
if (!isLanguageValid(attr.getAudioLanguage()))
|
||||||
|
throw new CommandLine.TypeConversionException("Audio language invalid: " + attr.getAudioLanguage());
|
||||||
|
if (!isLanguageValid(attr.getSubtitleLanguage()))
|
||||||
|
throw new CommandLine.TypeConversionException("Subtitle language invalid: " + attr.getSubtitleLanguage());
|
||||||
|
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.SubtitleTrackComparator;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
public class AttributeChangeProcessor {
|
||||||
|
private final SubtitleTrackComparator subtitleTrackComparator;
|
||||||
|
private final Set<String> commentaryKeywords;
|
||||||
|
private final Set<String> hearingImpairedKeywords;
|
||||||
|
private final Set<String> forcedKeywords;
|
||||||
|
|
||||||
|
public AttributeChangeProcessor(String[] preferredSubtitles, Set<String> forcedKeywords, Set<String> commentaryKeywords, Set<String> hearingImpairedKeywords) {
|
||||||
|
this.subtitleTrackComparator = new SubtitleTrackComparator(preferredSubtitles);
|
||||||
|
this.commentaryKeywords = commentaryKeywords;
|
||||||
|
this.hearingImpairedKeywords = hearingImpairedKeywords;
|
||||||
|
this.forcedKeywords = forcedKeywords;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TrackAttributes> filterForPossibleDefaults(List<TrackAttributes> tracks) {
|
||||||
|
Stream<TrackAttributes> attributes = tracks.stream();
|
||||||
|
|
||||||
|
if (true) { // TODO: config for including commentary
|
||||||
|
attributes = attributes
|
||||||
|
.filter(attr -> !attr.commentary())
|
||||||
|
.filter(attr -> {
|
||||||
|
if (attr.trackName() == null) return true;
|
||||||
|
return commentaryKeywords.stream().noneMatch(keyword -> keyword.compareToIgnoreCase(attr.trackName()) == 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true) { // TODO: config for including hearing impaired
|
||||||
|
attributes = attributes
|
||||||
|
.filter(attr -> !attr.hearingImpaired())
|
||||||
|
.filter(attr -> {
|
||||||
|
if (attr.trackName() == null) return true;
|
||||||
|
return hearingImpairedKeywords.stream().noneMatch(keyword -> keyword.compareToIgnoreCase(attr.trackName()) == 0);
|
||||||
|
});;
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributes
|
||||||
|
.filter(attr -> !attr.forced())
|
||||||
|
.filter(attr -> {
|
||||||
|
if (attr.trackName() == null) return true;
|
||||||
|
return forcedKeywords.stream().noneMatch(keyword -> keyword.compareToIgnoreCase(attr.trackName()) == 0);
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void findDefaultMatchAndApplyChanges(FileInfo fileInfo, AttributeConfig... configs) {
|
||||||
|
Map<String, List<TrackAttributes>> audiosByLanguage = new HashMap<>(fileInfo.getTracks().size());
|
||||||
|
Map<String, List<TrackAttributes>> subsByLanguage = new HashMap<>(fileInfo.getTracks().size());
|
||||||
|
filterForPossibleDefaults(fileInfo.getTracks()).forEach(track -> {
|
||||||
|
if (TrackType.AUDIO.equals(track.type()))
|
||||||
|
audiosByLanguage.computeIfAbsent(track.language(), (k) -> new ArrayList<>()).add(track);
|
||||||
|
else if (TrackType.SUBTITLES.equals(track.type()))
|
||||||
|
subsByLanguage.computeIfAbsent(track.language(), (k) -> new ArrayList<>()).add(track);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (AttributeConfig config : configs) {
|
||||||
|
if (("OFF".equals(config.getAudioLanguage()) || audiosByLanguage.containsKey(config.getAudioLanguage()))
|
||||||
|
&& ("OFF".equals(config.getSubtitleLanguage()) || subsByLanguage.containsKey(config.getSubtitleLanguage()))) {
|
||||||
|
fileInfo.setMatchedConfig(config);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// TODO: forced if OFF
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileInfo.getMatchedConfig() == null) return;
|
||||||
|
|
||||||
|
applyDefaultChanges(fileInfo, FileInfo::getAudioTracks, fileInfo.getMatchedConfig().getAudioLanguage(),
|
||||||
|
() -> audiosByLanguage.get(fileInfo.getMatchedConfig().getAudioLanguage()).get(0));
|
||||||
|
applyDefaultChanges(fileInfo, FileInfo::getSubtitleTracks, fileInfo.getMatchedConfig().getSubtitleLanguage(),
|
||||||
|
() -> subsByLanguage.get(fileInfo.getMatchedConfig().getSubtitleLanguage()).stream().max(subtitleTrackComparator).get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyDefaultChanges(FileInfo fileInfo, Function<FileInfo, List<TrackAttributes>> tracks, String language, Supplier<TrackAttributes> targetDefaultSupplier) {
|
||||||
|
tracks.apply(fileInfo).stream()
|
||||||
|
.filter(TrackAttributes::defaultt)
|
||||||
|
.forEach(attr -> fileInfo.getChanges().getDefaultTrack().put(attr, false));
|
||||||
|
if (!"OFF".equals(language)) {
|
||||||
|
TrackAttributes targetDefault = targetDefaultSupplier.get();
|
||||||
|
if (fileInfo.getChanges().getDefaultTrack().containsKey(targetDefault)) {
|
||||||
|
fileInfo.getChanges().getDefaultTrack().remove(targetDefault);
|
||||||
|
} else {
|
||||||
|
fileInfo.getChanges().getDefaultTrack().put(targetDefault, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void findForcedTracksAndApplyChanges(FileInfo fileInfo, boolean overwrite) {
|
||||||
|
Stream<TrackAttributes> forcedTracks = fileInfo.getTracks().stream()
|
||||||
|
.filter(track -> track.trackName() != null)
|
||||||
|
.filter(track -> forcedKeywords.stream().anyMatch(keyword -> track.trackName().toLowerCase().contains(keyword.toLowerCase(Locale.ROOT))));
|
||||||
|
|
||||||
|
if (overwrite) {
|
||||||
|
fileInfo.getTracks().stream().filter(TrackAttributes::forced).forEach(attr -> {
|
||||||
|
fileInfo.getChanges().getForcedTrack().put(attr, false);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
forcedTracks = forcedTracks.filter(attr -> !attr.forced());
|
||||||
|
}
|
||||||
|
|
||||||
|
forcedTracks.forEach(attr -> {
|
||||||
|
fileInfo.getChanges().getForcedTrack().put(attr, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void findCommentaryTracksAndApplyChanges(FileInfo fileInfo) {
|
||||||
|
fileInfo.getTracks().stream()
|
||||||
|
.filter(track -> !track.commentary())
|
||||||
|
.filter(track -> track.trackName() != null)
|
||||||
|
.filter(track -> commentaryKeywords.stream().anyMatch(keyword -> track.trackName().toLowerCase().contains(keyword.toLowerCase(Locale.ROOT))))
|
||||||
|
.forEach(attr -> {
|
||||||
|
fileInfo.getChanges().getCommentaryTrack().put(attr, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void findHearingImpairedTracksAndApplyChanges(FileInfo fileInfo) {
|
||||||
|
fileInfo.getTracks().stream()
|
||||||
|
.filter(track -> !track.hearingImpaired())
|
||||||
|
.filter(track -> track.trackName() != null)
|
||||||
|
.filter(track -> hearingImpairedKeywords.stream().anyMatch(keyword -> track.trackName().toLowerCase().contains(keyword.toLowerCase(Locale.ROOT))))
|
||||||
|
.forEach(attr -> {
|
||||||
|
fileInfo.getChanges().getHearingImpairedTrack().put(attr, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.exceptions.MkvToolNixException;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfo;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.InputConfig;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ResultStatistic;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
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.List;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AttributeUpdater {
|
||||||
|
|
||||||
|
protected final InputConfig config;
|
||||||
|
protected final FileProcessor fileProcessor;
|
||||||
|
protected final AttributeChangeProcessor attributeChangeProcessor;
|
||||||
|
protected final ResultStatistic statistic = ResultStatistic.getInstance();
|
||||||
|
|
||||||
|
private final ExecutorService executor;
|
||||||
|
|
||||||
|
public AttributeUpdater(InputConfig config, FileProcessor fileProcessor, AttributeChangeProcessor attributeChangeProcessor) {
|
||||||
|
this.config = config;
|
||||||
|
this.fileProcessor = fileProcessor;
|
||||||
|
this.attributeChangeProcessor = attributeChangeProcessor;
|
||||||
|
this.executor = Executors.newFixedThreadPool(config.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 = getFiles();
|
||||||
|
|
||||||
|
progressBar.maxHint(files.size());
|
||||||
|
progressBar.refresh();
|
||||||
|
|
||||||
|
files.forEach(file -> executor.submit(() -> {
|
||||||
|
process(file);
|
||||||
|
progressBar.step();
|
||||||
|
}));
|
||||||
|
|
||||||
|
executor.shutdown();
|
||||||
|
executor.awaitTermination(1, TimeUnit.DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeLastExecutionDate();
|
||||||
|
|
||||||
|
statistic.stopTimer();
|
||||||
|
statistic.print();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract List<File> getFiles();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
protected abstract void process(File file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persist file changes.
|
||||||
|
*
|
||||||
|
* @param fileInfo contains information about file and desired configuration.
|
||||||
|
*/
|
||||||
|
protected void checkStatusAndUpdate(FileInfo fileInfo) {
|
||||||
|
if (!fileInfo.getChanges().isEmpty()) {
|
||||||
|
statistic.changePlanned();
|
||||||
|
|
||||||
|
if (config.isSafeMode()) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fileProcessor.update(fileInfo);
|
||||||
|
statistic.changeSuccessful();
|
||||||
|
log.info("Commited {} to '{}'", fileInfo.getMatchedConfig().toStringShort(), fileInfo.getFile().getPath());
|
||||||
|
} catch (IOException | MkvToolNixException e) {
|
||||||
|
statistic.changeFailed();
|
||||||
|
log.warn("Couldn't commit {} to '{}'", fileInfo.getMatchedConfig().toStringShort(), fileInfo.getFile().getPath(), e);
|
||||||
|
}
|
||||||
|
} else if (fileInfo.getChanges().isEmpty()) {
|
||||||
|
statistic.unchanged();
|
||||||
|
} else {
|
||||||
|
statistic.unknownFailed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// should this be here?
|
||||||
|
// protected void writeLastExecutionDate() {
|
||||||
|
// if (config.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.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,42 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.exceptions.MkvToolNixException;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.Cache;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfo;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class CachedFileProcessor implements FileProcessor {
|
||||||
|
private final FileProcessor processor;
|
||||||
|
Cache<String, List<File>> fileCache = new Cache<>();
|
||||||
|
Cache<Pair<String, Integer>, List<File>> directoryCache = new Cache<>();
|
||||||
|
Cache<File, FileInfo> attributeCache = new Cache<>();
|
||||||
|
|
||||||
|
public CachedFileProcessor(FileProcessor processor) {
|
||||||
|
this.processor = processor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<File> loadFiles(String path) {
|
||||||
|
return fileCache.retrieve(path, processor::loadFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<File> loadDirectory(String path, int depth) {
|
||||||
|
return directoryCache.retrieve(Pair.of(path, depth), key -> processor.loadDirectory(key.getLeft(), key.getRight()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileInfo readAttributes(File file) {
|
||||||
|
return attributeCache.retrieve(file, processor::readAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(FileInfo fileInfo) throws IOException, MkvToolNixException {
|
||||||
|
processor.update(fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfo;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.InputConfig;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.tongfei.progressbar.ProgressBarBuilder;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CoherentAttributeUpdater extends SingleFileAttributeUpdater {
|
||||||
|
|
||||||
|
public CoherentAttributeUpdater(InputConfig config, FileProcessor processor, AttributeChangeProcessor attributeChangeProcessor) {
|
||||||
|
super(config, processor, attributeChangeProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ProgressBarBuilder pbBuilder() {
|
||||||
|
return super.pbBuilder()
|
||||||
|
.setUnit(" directories", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<File> getFiles() {
|
||||||
|
return fileProcessor.loadDirectory(config.getLibraryPath().getPath(), config.getCoherent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(File rootDir) {
|
||||||
|
if (rootDir.isFile()) {
|
||||||
|
super.process(rootDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<File> files = fileProcessor.loadFiles(rootDir.getPath());
|
||||||
|
Set<FileInfo> matchedFiles = new HashSet<>(files.size() * 2);
|
||||||
|
|
||||||
|
for (AttributeConfig config: config.getAttributeConfig()) {
|
||||||
|
AttributeConfig matchedConfig = findMatch(config, matchedFiles, files);
|
||||||
|
|
||||||
|
if (matchedConfig == null) continue;
|
||||||
|
if (matchedFiles.size() != files.size()) {
|
||||||
|
log.warn("Skip applying changes: Found coherent match, but matched count is different than file count (matched: {}, files: {}, dir: {})",
|
||||||
|
matchedFiles.size(), files.size(), rootDir.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
matchedFiles.forEach(fileInfo -> {
|
||||||
|
attributeChangeProcessor.findForcedTracksAndApplyChanges(fileInfo, this.config.isOverwriteForced());
|
||||||
|
attributeChangeProcessor.findCommentaryTracksAndApplyChanges(fileInfo);
|
||||||
|
attributeChangeProcessor.findHearingImpairedTracksAndApplyChanges(fileInfo);
|
||||||
|
|
||||||
|
checkStatusAndUpdate(fileInfo);
|
||||||
|
});
|
||||||
|
return; // match was found and process must be stopped
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't match any config at current level. Resetting changes and trying to one level deeper
|
||||||
|
matchedFiles.forEach(fileInfo -> {
|
||||||
|
fileInfo.resetChanges();
|
||||||
|
fileInfo.setMatchedConfig(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (config.isForceCoherent()) {
|
||||||
|
log.info("No coherent match found, aborting: {}", rootDir.getPath());
|
||||||
|
statistic.increaseUnchangedBy(files.size());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("No coherent match found, trying to find coherent match in child directories: {}", rootDir.getPath());
|
||||||
|
for (File dir: fileProcessor.loadDirectory(rootDir.getPath(), 1)) this.process(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AttributeConfig findMatch(AttributeConfig config, Set<FileInfo> matchedFiles, List<File> files) {
|
||||||
|
AttributeConfig matchedConfig = null;
|
||||||
|
matchedFiles.clear();
|
||||||
|
|
||||||
|
for (File file: files) {
|
||||||
|
FileInfo fileInfo = fileProcessor.readAttributes(file);
|
||||||
|
fileInfo.resetChanges();
|
||||||
|
fileInfo.setMatchedConfig(null);
|
||||||
|
|
||||||
|
if (fileInfo.getTracks().isEmpty()) {
|
||||||
|
log.warn("No attributes found for file {}", file);
|
||||||
|
statistic.unknownFailed();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangeProcessor.findDefaultMatchAndApplyChanges(fileInfo, config);
|
||||||
|
|
||||||
|
if (matchedConfig == null) matchedConfig = fileInfo.getMatchedConfig();
|
||||||
|
if (matchedConfig == null || matchedConfig != fileInfo.getMatchedConfig()) {
|
||||||
|
matchedConfig = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
matchedFiles.add(fileInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return matchedConfig;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.exceptions.MkvToolNixException;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfo;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface FileProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param path leads to one file directly or a directory which will be loaded recursively
|
||||||
|
* @return list of all files within the directory
|
||||||
|
*/
|
||||||
|
List<File> loadFiles(String path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load only directories and files at depth, ignoring everything between root dir and dir at depth.
|
||||||
|
* E.g. with file structure /base/depth1/depth2/depth3.file
|
||||||
|
* - with depth 1: return /base/depth1/
|
||||||
|
* - with depth 2: returns /base/depth1/depth2/
|
||||||
|
*
|
||||||
|
* @param path directory which will be loaded recursively until depth
|
||||||
|
* @param depth limit directory crawling
|
||||||
|
* @return list of directory at depth
|
||||||
|
*/
|
||||||
|
List<File> loadDirectory(String path, int depth);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load track information from file.
|
||||||
|
*
|
||||||
|
* @param file Takes the file from which the attributes will be returned
|
||||||
|
* @return list of all important attributes
|
||||||
|
*/
|
||||||
|
FileInfo readAttributes(File file);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the file.
|
||||||
|
*
|
||||||
|
* @param fileInfo information used to update file
|
||||||
|
* @throws IOException when error occurs accessing file retrieving information
|
||||||
|
* @throws MkvToolNixException when error occurs while sending query to mkvpropedit
|
||||||
|
*/
|
||||||
|
void update(FileInfo fileInfo) throws IOException, MkvToolNixException;
|
||||||
|
|
||||||
|
default InputStream run(String[] command) throws IOException {
|
||||||
|
return Runtime.getRuntime().exec(command).getInputStream();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.exceptions.MkvToolNixException;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.FileFilter;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.*;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.logging.log4j.core.util.IOUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.FileUtils.getPathFor;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MkvFileProcessor implements FileProcessor {
|
||||||
|
protected final File mkvToolNixInstallation;
|
||||||
|
protected final FileFilter fileFilter;
|
||||||
|
|
||||||
|
private final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
|
||||||
|
private static final Set<String> fileExtensions = new HashSet<>(Set.of(".mkv", ".mka", ".mks", ".mk3d"));
|
||||||
|
|
||||||
|
private static final String DEFAULT_TRACK = "--edit track:%s --set flag-default=%s";
|
||||||
|
private static final String FORCED_TRACK = "--edit track:%s --set flag-forced=%s";
|
||||||
|
private static final String COMMENTARY_TRACK = "--edit track:%s --set flag-commentary=%s";
|
||||||
|
private static final String HEARING_IMPAIRED_TRACK = "--edit track:%s --set flag-hearing-impaired=%s";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<File> loadFiles(String path) {
|
||||||
|
try (Stream<Path> paths = Files.walk(Paths.get(path))) {
|
||||||
|
return paths
|
||||||
|
.filter(Files::isRegularFile)
|
||||||
|
.map(Path::toFile)
|
||||||
|
.filter(file -> fileFilter.accept(file, fileExtensions))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Couldn't find file or directory!", e);
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
// does this load /arst/arst & /arst ?
|
||||||
|
public List<File> loadDirectory(String path, int depth) {
|
||||||
|
File rootDir = Path.of(path).toFile();
|
||||||
|
if (!rootDir.exists()) {
|
||||||
|
log.error("Couldn't find file or directory!");
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<File> result = new ArrayList<>();
|
||||||
|
exploreDirectory(rootDir, 0, depth, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively explores directories to find items at the target depth.
|
||||||
|
*
|
||||||
|
* @param currentDir The current directory being explored
|
||||||
|
* @param currentDepth The current depth level
|
||||||
|
* @param targetDepth The target depth to collect files
|
||||||
|
* @param result The collection to store found files
|
||||||
|
*/
|
||||||
|
private static void exploreDirectory(File currentDir, int currentDepth, int targetDepth, List<File> result) {
|
||||||
|
if (currentDepth == targetDepth) {
|
||||||
|
result.add(currentDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all files and directories in the current directory
|
||||||
|
File[] files = currentDir.listFiles();
|
||||||
|
if (files == null) return;
|
||||||
|
|
||||||
|
// Recursively explore subdirectories
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
exploreDirectory(file, currentDepth + 1, targetDepth, result);
|
||||||
|
} else if (currentDepth + 1 == targetDepth) {
|
||||||
|
// If files at the next level would be at the target depth, include them
|
||||||
|
result.add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@Override
|
||||||
|
public FileInfo readAttributes(File file) {
|
||||||
|
FileInfo fileInfo = new FileInfo(file);
|
||||||
|
try {
|
||||||
|
String[] command = new String[]{
|
||||||
|
getPathFor(mkvToolNixInstallation, MkvToolNix.MKV_MERGE).getAbsolutePath(),
|
||||||
|
"--identify",
|
||||||
|
"--identification-format",
|
||||||
|
"json",
|
||||||
|
file.getAbsolutePath()
|
||||||
|
};
|
||||||
|
|
||||||
|
log.debug("Executing: {}", String.join(" ", command));
|
||||||
|
InputStream inputStream = run(command);
|
||||||
|
Map<String, Object> jsonMap = mapper.readValue(inputStream, Map.class);
|
||||||
|
List<Map<String, Object>> tracks = (List<Map<String, Object>>) jsonMap.get("tracks");
|
||||||
|
if (tracks != null) {
|
||||||
|
for (Map<String, Object> attribute : tracks) {
|
||||||
|
if (!"video".equals(attribute.get("type"))) {
|
||||||
|
Map<String, Object> properties = (Map<String, Object>) attribute.get("properties");
|
||||||
|
fileInfo.addTrack(new TrackAttributes(
|
||||||
|
(int) properties.get("number"),
|
||||||
|
(String) properties.get("language"),
|
||||||
|
(String) properties.get("track_name"),
|
||||||
|
(Boolean) properties.getOrDefault("default_track", false),
|
||||||
|
(Boolean) properties.getOrDefault("forced_track", false),
|
||||||
|
(Boolean) properties.getOrDefault("commentary_track", false),
|
||||||
|
(Boolean) properties.getOrDefault("flag_hearing_impaired", false),
|
||||||
|
TrackType.valueOf(((String) attribute.get("type")).toUpperCase(Locale.ENGLISH))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.warn("Couldn't retrieve information of {}", file.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("File attributes of '{}': {}", file.getAbsolutePath(), fileInfo.getTracks());
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("File could not be found or loaded: ", e);
|
||||||
|
System.out.println("File could not be found or loaded: " + file.getAbsolutePath());
|
||||||
|
}
|
||||||
|
return fileInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void update(FileInfo fileInfo) throws IOException, MkvToolNixException {
|
||||||
|
String[] command = getUpdateCommand(fileInfo);
|
||||||
|
log.debug("Executing '{}'", String.join(" ", command));
|
||||||
|
InputStream inputstream = run(command);
|
||||||
|
String output = IOUtils.toString(new InputStreamReader(inputstream));
|
||||||
|
log.debug("Result: {}", output.replaceAll("\\n", " '"));
|
||||||
|
if (output.contains("Error")) throw new MkvToolNixException(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] getUpdateCommand(FileInfo fileInfo) {
|
||||||
|
List<String> command = new ArrayList<>();
|
||||||
|
command.add(getPathFor(mkvToolNixInstallation, MkvToolNix.MKV_PROP_EDIT).getAbsolutePath());
|
||||||
|
command.add(String.format(fileInfo.getFile().getAbsolutePath()));
|
||||||
|
|
||||||
|
PlannedChange changes = fileInfo.getChanges();
|
||||||
|
changes.getDefaultTrack().forEach((key, value) -> command.addAll(format(DEFAULT_TRACK, key.id(), value ? 1 : 0)));
|
||||||
|
changes.getForcedTrack().forEach((key, value) -> command.addAll(format(FORCED_TRACK, key.id(), value ? 1 : 0)));
|
||||||
|
changes.getCommentaryTrack().forEach((key, value) -> command.addAll(format(COMMENTARY_TRACK, key.id(), value ? 1 : 0)));
|
||||||
|
changes.getHearingImpairedTrack().forEach((key, value) -> command.addAll(format(HEARING_IMPAIRED_TRACK, key.id(), value ? 1 : 0)));
|
||||||
|
return command.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> format(String format, Object... args) {
|
||||||
|
return Arrays.asList(String.format(format, args).split(" "));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfo;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.InputConfig;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.tongfei.progressbar.ProgressBarBuilder;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class SingleFileAttributeUpdater extends AttributeUpdater {
|
||||||
|
|
||||||
|
public SingleFileAttributeUpdater(InputConfig config, FileProcessor processor, AttributeChangeProcessor attributeChangeProcessor) {
|
||||||
|
super(config, processor, attributeChangeProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ProgressBarBuilder pbBuilder() {
|
||||||
|
return super.pbBuilder()
|
||||||
|
.setUnit(" files", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected List<File> getFiles() {
|
||||||
|
return fileProcessor.loadFiles(config.getLibraryPath().getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(File file) {
|
||||||
|
FileInfo fileInfo = fileProcessor.readAttributes(file);
|
||||||
|
|
||||||
|
if (fileInfo.getTracks().isEmpty()) {
|
||||||
|
log.warn("No attributes found for file {}", file);
|
||||||
|
statistic.unknownFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
attributeChangeProcessor.findDefaultMatchAndApplyChanges(fileInfo, config.getAttributeConfig());
|
||||||
|
attributeChangeProcessor.findForcedTracksAndApplyChanges(fileInfo, config.isOverwriteForced());
|
||||||
|
attributeChangeProcessor.findCommentaryTracksAndApplyChanges(fileInfo);
|
||||||
|
attributeChangeProcessor.findHearingImpairedTracksAndApplyChanges(fileInfo);
|
||||||
|
|
||||||
|
checkStatusAndUpdate(fileInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation;
|
||||||
|
|
||||||
|
import jakarta.validation.Constraint;
|
||||||
|
import jakarta.validation.Payload;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Constraint(validatedBy = ValidFileValidator.class)
|
||||||
|
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ValidFile {
|
||||||
|
String message() default "File does not exist";
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation;
|
||||||
|
|
||||||
|
import jakarta.validation.ConstraintValidator;
|
||||||
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class ValidFileValidator implements ConstraintValidator<ValidFile, File> {
|
||||||
|
@Override
|
||||||
|
public void initialize(ValidFile constraintAnnotation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(File file, ConstraintValidatorContext context) {
|
||||||
|
return file != null && file.exists();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation;
|
||||||
|
|
||||||
|
import jakarta.validation.Constraint;
|
||||||
|
import jakarta.validation.Payload;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Constraint(validatedBy = ValidMkvToolNixValidator.class)
|
||||||
|
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ValidMkvToolNix {
|
||||||
|
String message() default "MkvToolNix does not exist";
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.MkvToolNix;
|
||||||
|
import jakarta.validation.ConstraintValidator;
|
||||||
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.FileUtils.getPathFor;
|
||||||
|
|
||||||
|
public class ValidMkvToolNixValidator implements ConstraintValidator<ValidMkvToolNix, File> {
|
||||||
|
@Override
|
||||||
|
public void initialize(ValidMkvToolNix constraintAnnotation) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(File file, ConstraintValidatorContext context) {
|
||||||
|
return file != null && file.exists()
|
||||||
|
&& getPathFor(file, MkvToolNix.MKV_MERGE).exists()
|
||||||
|
&& getPathFor(file, MkvToolNix.MKV_PROP_EDIT).exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.CommandRunner;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.InputConfig;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.ValidationUtil;
|
||||||
|
import jakarta.validation.ConstraintViolation;
|
||||||
|
import jakarta.validation.Validator;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class ValidationExecutionStrategy implements CommandLine.IExecutionStrategy {
|
||||||
|
|
||||||
|
public int execute(CommandLine.ParseResult parseResult) {
|
||||||
|
if (!parseResult.isVersionHelpRequested() && !parseResult.isUsageHelpRequested()) validate(parseResult.commandSpec());
|
||||||
|
return new CommandLine.RunLast().execute(parseResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void validate(CommandLine.Model.CommandSpec spec) {
|
||||||
|
Validator validator = ValidationUtil.getValidator();
|
||||||
|
Set<ConstraintViolation<InputConfig>> violations = validator.validate(((CommandRunner)spec.userObject()).getConfig());
|
||||||
|
|
||||||
|
if (!violations.isEmpty()) {
|
||||||
|
StringBuilder errors = new StringBuilder();
|
||||||
|
for (ConstraintViolation<InputConfig> violation : violations) {
|
||||||
|
errors.append(violation.getPropertyPath()).append(" ").append(violation.getMessage()).append("\n");
|
||||||
|
}
|
||||||
|
throw new CommandLine.ParameterException(spec.commandLine(), errors.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,15 +2,38 @@ 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 AttributeConfig {
|
public class AttributeConfig {
|
||||||
private final String audioLanguage;
|
private final String audioLanguage;
|
||||||
private final String subtitleLanguage;
|
private final String subtitleLanguage;
|
||||||
|
|
||||||
|
public static AttributeConfig of(String audioLanguage, String subtitleLanguage) {
|
||||||
|
return new AttributeConfig(audioLanguage, subtitleLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
AttributeConfig that = (AttributeConfig) o;
|
||||||
|
return Objects.equals(audioLanguage, that.audioLanguage) && Objects.equals(subtitleLanguage, that.subtitleLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(audioLanguage, subtitleLanguage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toStringShort() {
|
||||||
|
return audioLanguage + ":" + subtitleLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "AttributeConfig{"
|
return "AttributeConfig{"
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum ConfigProperty {
|
|
||||||
CONFIG_PATH("config", "Path to config file"),
|
|
||||||
LIBRARY("library", "Path to library"),
|
|
||||||
SAFE_MODE("safe-mode", "Test run (no files will be changes)"),
|
|
||||||
THREADS("threads", "thread count (default: 2)"),
|
|
||||||
INCLUDE_PATTERN("include-pattern", "Include files matching pattern"),
|
|
||||||
MKV_TOOL_NIX("mkvtoolnix", "Path to mkv tool nix installation"),
|
|
||||||
FORCED_KEYWORDS("forcedKeywords", "Additional keywords to identify forced tracks, combines with config file"),
|
|
||||||
EXCLUDE_DIRECTORY("exclude-directories", "Directories to be excluded, combines with config file"),
|
|
||||||
HELP("help", "\"for help this is\" - Yoda"),
|
|
||||||
VERSION("version", "Display version");
|
|
||||||
|
|
||||||
private final String property;
|
|
||||||
private final String description;
|
|
||||||
|
|
||||||
public String desc() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String prop() {
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
|
|
||||||
@Log4j2
|
|
||||||
@Getter
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class FileAttribute {
|
|
||||||
private final int id;
|
|
||||||
private final String language;
|
|
||||||
private final String trackName;
|
|
||||||
private final boolean defaultTrack;
|
|
||||||
private final boolean forcedTrack;
|
|
||||||
private final LaneType type;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "[" + "id=" + id +
|
|
||||||
", language='" + language + '\'' +
|
|
||||||
", trackName='" + trackName + '\'' +
|
|
||||||
", defaultTrack=" + defaultTrack +
|
|
||||||
", forcedTrack=" + forcedTrack +
|
|
||||||
", type=" + type +
|
|
||||||
']';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class FileInfo {
|
||||||
|
private final File file;
|
||||||
|
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
private final List<TrackAttributes> tracks = new ArrayList<>();
|
||||||
|
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
private final List<TrackAttributes> audioTracks = new ArrayList<>();
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
private final List<TrackAttributes> subtitleTracks = new ArrayList<>();
|
||||||
|
|
||||||
|
private PlannedChange changes = new PlannedChange();
|
||||||
|
@Setter
|
||||||
|
private AttributeConfig matchedConfig;
|
||||||
|
|
||||||
|
public void addTrack(TrackAttributes track) {
|
||||||
|
tracks.add(track);
|
||||||
|
if (TrackType.AUDIO.equals(track.type())) audioTracks.add(track);
|
||||||
|
else if (TrackType.SUBTITLES.equals(track.type())) subtitleTracks.add(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTracks(Collection<TrackAttributes> tracks) {
|
||||||
|
for (TrackAttributes track : tracks) addTrack(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TrackAttributes> getTracks() {
|
||||||
|
return Collections.unmodifiableList(tracks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TrackAttributes> getAudioTracks() {
|
||||||
|
return Collections.unmodifiableList(audioTracks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TrackAttributes> getSubtitleTracks() {
|
||||||
|
return Collections.unmodifiableList(subtitleTracks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetChanges() {
|
||||||
|
changes = new PlannedChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
public class FileInfoDto {
|
|
||||||
private Set<FileAttribute> defaultAudioLanes = new HashSet<>();
|
|
||||||
private Set<FileAttribute> defaultSubtitleLanes = new HashSet<>();
|
|
||||||
private Set<FileAttribute> desiredForcedSubtitleLanes;
|
|
||||||
private FileAttribute desiredAudioLane;
|
|
||||||
private FileAttribute desiredSubtitleLane;
|
|
||||||
|
|
||||||
public boolean isUnableToApplyConfig() {
|
|
||||||
return desiredAudioLane == null && desiredSubtitleLane == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAlreadySuitable() {
|
|
||||||
return defaultAudioLanes.contains(desiredAudioLane) && defaultSubtitleLanes.contains(desiredSubtitleLane);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isChangeNecessary() {
|
|
||||||
return isAudioDifferent() || isSubtitleDifferent() || areForcedTracksDifferent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAudioDifferent() {
|
|
||||||
return desiredAudioLane != null &&
|
|
||||||
(defaultAudioLanes == null || !defaultAudioLanes.contains(desiredAudioLane));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSubtitleDifferent() {
|
|
||||||
return desiredSubtitleLane != null &&
|
|
||||||
(defaultSubtitleLanes == null || !defaultSubtitleLanes.contains(desiredSubtitleLane));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean areForcedTracksDifferent() {
|
|
||||||
return desiredForcedSubtitleLanes.size() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "[" + "defaultAudioLanes=" + defaultAudioLanes +
|
|
||||||
", defaultSubtitleLanes=" + defaultSubtitleLanes +
|
|
||||||
", desiredForcedSubtitleLanes=" + desiredForcedSubtitleLanes +
|
|
||||||
", desiredAudioLane=" + desiredAudioLane +
|
|
||||||
", desiredSubtitleLane=" + desiredSubtitleLane +
|
|
||||||
']';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.converter.AttributeConfigConverter;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation.ValidFile;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation.ValidMkvToolNix;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import picocli.CommandLine.Option;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@CommandLine.Command
|
||||||
|
public class InputConfig implements CommandLine.IVersionProvider {
|
||||||
|
|
||||||
|
private File configPath;
|
||||||
|
|
||||||
|
@CommandLine.Spec
|
||||||
|
CommandLine.Model.CommandSpec spec;
|
||||||
|
|
||||||
|
@ValidFile(message = "does not exist")
|
||||||
|
@CommandLine.Parameters(description = "path to library")
|
||||||
|
private File libraryPath;
|
||||||
|
|
||||||
|
@Option(names = {"-a", "--attribute-config"}, arity = "1..*", converter = AttributeConfigConverter.class,
|
||||||
|
description = "List of audio:subtitle pairs used to match in order and update files accordingly (e.g. jpn:eng jpn:ger)")
|
||||||
|
private AttributeConfig[] attributeConfig = new AttributeConfig[0];
|
||||||
|
@ValidMkvToolNix(message = "does not exist")
|
||||||
|
@Option(names = {"-m", "--mkvtoolnix"}, defaultValue = "${DEFAULT_MKV_TOOL_NIX}", description = "path to mkvtoolnix installation")
|
||||||
|
private File mkvToolNix;
|
||||||
|
|
||||||
|
@Option(names = {"-s", "--safemode"}, description = "test run (no files will be changes)")
|
||||||
|
private boolean safeMode;
|
||||||
|
|
||||||
|
@Min(1)
|
||||||
|
@Option(names = {"-t", "--threads"}, defaultValue = "2", description = "thread count (default: ${DEFAULT-VALUE})")
|
||||||
|
private int threads;
|
||||||
|
|
||||||
|
@Min(0)
|
||||||
|
@Option(names = {"-c", "--coherent"}, description = "try to match all files in dir of depth with the same attribute config. Attempting increasing deeper levels until match is found (worst case applying config on single file basis)")
|
||||||
|
private Integer coherent;
|
||||||
|
@Option(names = {"-cf", "--force-coherent"}, description = "only applies changes if a coherent match was found for the specifically entered depth")
|
||||||
|
private boolean forceCoherent;
|
||||||
|
|
||||||
|
// TODO: implement usage
|
||||||
|
// @Option(names = {"-n", "--only-new-file"}, description = "sets filter-date to last successful execution (overwrites input of filter-date)")
|
||||||
|
// private boolean onlyNewFiles;
|
||||||
|
@Option(names = {"-d", "--filter-date"}, defaultValue = Option.NULL_VALUE, description = "only consider files created newer than entered date (format: \"dd.MM.yyyy-HH:mm:ss\")")
|
||||||
|
private Date filterDate;
|
||||||
|
@Option(names = {"-i", "--include-pattern"}, defaultValue = ".*", description = "include files matching pattern (default: \".*\")")
|
||||||
|
private Pattern includePattern;
|
||||||
|
@Option(names = {"-e", "--exclude"}, arity = "1..*",
|
||||||
|
description = "relative directories and files to be excluded (no wildcard)")
|
||||||
|
private Set<String> excluded = new HashSet<>();
|
||||||
|
|
||||||
|
|
||||||
|
@Option(names = {"-o", "-overwrite-forced"}, description = "remove all forced flags")
|
||||||
|
private boolean overwriteForced;
|
||||||
|
@Option(names = {"--forced-keywords"}, arity = "1..*", defaultValue = "forced, signs, songs", split = ", ",
|
||||||
|
description = "Keywords to identify forced tracks (Defaults will be overwritten; Default: ${DEFAULT-VALUE})")
|
||||||
|
private Set<String> forcedKeywords;
|
||||||
|
@Option(names = {"--commentary-keywords"}, arity = "1..*", defaultValue = "comment, commentary, director", split = ", ",
|
||||||
|
description = "Keywords to identify commentary tracks (Defaults will be overwritten; Default: ${DEFAULT-VALUE})")
|
||||||
|
private Set<String> commentaryKeywords;
|
||||||
|
@Option(names = {"--hearing-impaired"}, arity = "1..*", defaultValue = "SDH, CC", split = ", ",
|
||||||
|
description = "Keywords to identify hearing impaired tracks (Defaults will be overwritten; Default: ${DEFAULT-VALUE}")
|
||||||
|
private Set<String> hearingImpaired;
|
||||||
|
@Option(names = {"--preferred-subtitles"}, arity = "1..*", defaultValue = "unstyled", split = ", ",
|
||||||
|
description = "Keywords to prefer specific subtitle tracks (Defaults will be overwritten; Default: ${DEFAULT-VALUE})")
|
||||||
|
private Set<String> preferredSubtitles;
|
||||||
|
@Option(names = {"--debug"}, description = "Enable debug logging")
|
||||||
|
private boolean debug;
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Set default value into system properties to picocli can read the conditional value
|
||||||
|
System.setProperty("DEFAULT_MKV_TOOL_NIX", SystemUtils.IS_OS_WINDOWS ? "C:\\Program Files\\MKVToolNix" : "/usr/bin/");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new StringJoiner(", ", InputConfig.class.getSimpleName() + "[", "]")
|
||||||
|
.add("configPath=" + configPath)
|
||||||
|
.add("spec=" + spec)
|
||||||
|
.add("libraryPath=" + libraryPath)
|
||||||
|
.add("attributeConfig=" + Arrays.toString(attributeConfig))
|
||||||
|
.add("mkvToolNix=" + mkvToolNix)
|
||||||
|
.add("safeMode=" + safeMode)
|
||||||
|
.add("threads=" + threads)
|
||||||
|
.add("coherent=" + coherent)
|
||||||
|
.add("forceCoherent=" + forceCoherent)
|
||||||
|
.add("filterDate=" + filterDate)
|
||||||
|
.add("includePattern=" + includePattern)
|
||||||
|
.add("excluded=" + excluded)
|
||||||
|
.add("overwriteForced=" + overwriteForced)
|
||||||
|
.add("forcedKeywords=" + forcedKeywords)
|
||||||
|
.add("commentaryKeywords=" + commentaryKeywords)
|
||||||
|
.add("hearingImpaired=" + hearingImpaired)
|
||||||
|
.add("preferredSubtitles=" + preferredSubtitles)
|
||||||
|
.add("debug=" + debug)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String[] getVersion() throws Exception {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -4,8 +4,8 @@ import lombok.AllArgsConstructor;
|
|||||||
|
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum MkvToolNix {
|
public enum MkvToolNix {
|
||||||
MKV_MERGER("mkvmerge.exe"),
|
MKV_MERGE("mkvmerge"),
|
||||||
MKV_PROP_EDIT("mkvpropedit.exe");
|
MKV_PROP_EDIT("mkvpropedit");
|
||||||
|
|
||||||
private final String file;
|
private final String file;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class PlannedChange {
|
||||||
|
private final Map<TrackAttributes, Boolean> defaultTrack = new HashMap<>();
|
||||||
|
private final Map<TrackAttributes, Boolean> forcedTrack = new HashMap<>();
|
||||||
|
private final Map<TrackAttributes, Boolean> commentaryTrack = new HashMap<>();
|
||||||
|
private final Map<TrackAttributes, Boolean> hearingImpairedTrack = new HashMap<>();
|
||||||
|
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return defaultTrack.isEmpty() && forcedTrack.isEmpty() && commentaryTrack.isEmpty() && hearingImpairedTrack.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,59 +1,65 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||||
|
|
||||||
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 PRINT_TEMPLATE = "Total: %s, Changing: %s (Successful: %s, Failed %s), Unchanged: %s, Excluded: %s, Unknown/Failed: %s\nRuntime: %s";
|
||||||
"├─ Should change: %s%n" +
|
private static ResultStatistic instance;
|
||||||
"│ ├─ Failed changing: %s%n" +
|
|
||||||
"│ └─ Successfully changed: %s%n" +
|
|
||||||
"├─ No suitable config found: %s%n" +
|
|
||||||
"├─ Already fit config: %s%n" +
|
|
||||||
"└─ Failed: %s%n" +
|
|
||||||
"Runtime: %ss";
|
|
||||||
|
|
||||||
private int filesTotal = 0;
|
private int changePlanned = 0;
|
||||||
|
private int changeFailed = 0;
|
||||||
|
private int changeSuccessful = 0;
|
||||||
|
private int unchanged = 0;
|
||||||
|
private int excluded = 0;
|
||||||
|
private int unknownFailed = 0;
|
||||||
|
|
||||||
private int shouldChange = 0;
|
|
||||||
private int failedChanging = 0;
|
|
||||||
private int successfullyChanged = 0;
|
|
||||||
|
|
||||||
private int noSuitableConfigFound = 0;
|
|
||||||
private int alreadyFits = 0;
|
|
||||||
private int failed = 0;
|
|
||||||
|
|
||||||
@Getter(AccessLevel.NONE)
|
|
||||||
private long startTime = 0;
|
private long startTime = 0;
|
||||||
private long runtime = 0;
|
private long runtime = 0;
|
||||||
|
|
||||||
public synchronized void total() {
|
public static ResultStatistic getInstance() {
|
||||||
filesTotal++;
|
return getInstance(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void shouldChange() {
|
public static ResultStatistic getInstance(boolean reset) {
|
||||||
shouldChange++;
|
if (instance == null || reset) {
|
||||||
|
instance = new ResultStatistic();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void success() {
|
public int total() {
|
||||||
successfullyChanged++;
|
return changePlanned + unchanged + excluded + unknownFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void failedChanging() {
|
public synchronized void changePlanned() {
|
||||||
failedChanging++;
|
changePlanned++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void noSuitableConfigFound() {
|
public synchronized void changeSuccessful() {
|
||||||
noSuitableConfigFound++;
|
changeSuccessful++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void alreadyFits() {
|
public synchronized void changeFailed() {
|
||||||
alreadyFits++;
|
changeFailed++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void failure() {
|
public synchronized void unchanged() {
|
||||||
failed++;
|
unchanged++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void increaseUnchangedBy(int amount) {
|
||||||
|
unchanged += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void excluded() {
|
||||||
|
excluded++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void unknownFailed() {
|
||||||
|
unknownFailed++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startTimer() {
|
public void startTimer() {
|
||||||
@@ -64,9 +70,31 @@ public class ResultStatistic {
|
|||||||
runtime = System.currentTimeMillis() - startTime;
|
runtime = System.currentTimeMillis() - startTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String formatTimer() {
|
||||||
|
int seconds = (int) (runtime / 1000);
|
||||||
|
int minutes = seconds / 60;
|
||||||
|
int hours = minutes / 60;
|
||||||
|
int days = hours / 24;
|
||||||
|
|
||||||
|
if (days >= 1) {
|
||||||
|
return String.format("%sd %sh %sm %ss", days, hours % 24, minutes % 60, seconds % 60);
|
||||||
|
} else if (hours >= 1) {
|
||||||
|
return String.format("%sh %sm %ss", hours, minutes % 60, seconds % 60);
|
||||||
|
} else if (minutes >= 1) {
|
||||||
|
return String.format("%sm %ss", minutes, seconds % 60);
|
||||||
|
} else {
|
||||||
|
return String.format("%ss", seconds % 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void print() {
|
||||||
|
String result = this.toString();
|
||||||
|
System.out.println(result);
|
||||||
|
log.info(result);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return String.format(result, filesTotal, shouldChange, failedChanging, successfullyChanged,
|
return String.format(PRINT_TEMPLATE, total(), changePlanned, changeSuccessful, changeFailed, unchanged, excluded, unknownFailed, formatTimer());
|
||||||
noSuitableConfigFound, alreadyFits, failed, runtime / 1000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public record TrackAttributes(int id, String language, String trackName,
|
||||||
|
boolean defaultt, boolean forced, boolean commentary, boolean hearingImpaired,
|
||||||
|
TrackType type) {
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
TrackAttributes attribute = (TrackAttributes) o;
|
||||||
|
return id == attribute.id
|
||||||
|
&& defaultt == attribute.defaultt
|
||||||
|
&& forced == attribute.forced
|
||||||
|
&& commentary == attribute.commentary
|
||||||
|
&& hearingImpaired == attribute.hearingImpaired
|
||||||
|
&& Objects.equals(language, attribute.language)
|
||||||
|
&& Objects.equals(trackName, attribute.trackName)
|
||||||
|
&& type == attribute.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[" + "id=" + id +
|
||||||
|
", language='" + language + '\'' +
|
||||||
|
", trackName='" + trackName + '\'' +
|
||||||
|
", defaultt=" + defaultt +
|
||||||
|
", forced=" + forced +
|
||||||
|
", commentary=" + commentary +
|
||||||
|
", hearingImpaired=" + hearingImpaired +
|
||||||
|
", type=" + type +
|
||||||
|
']';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||||
|
|
||||||
public enum LaneType {
|
public enum TrackType {
|
||||||
AUDIO,
|
AUDIO,
|
||||||
SUBTITLES;
|
SUBTITLES;
|
||||||
}
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
|
||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ConfigProperty;
|
|
||||||
import org.apache.commons.cli.Option;
|
|
||||||
|
|
||||||
public class CommandLineOptionsUtil {
|
|
||||||
public static Option optionOf(ConfigProperty property, String opt, boolean hasArg) {
|
|
||||||
return optionOf(property, opt, hasArg ? 1 : 0, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Option optionOf(ConfigProperty property, String opt, boolean hasArg, boolean required) {
|
|
||||||
return optionOf(property, opt, hasArg ? 1 : 0, required);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Option optionOf(ConfigProperty property, String opt, int args, boolean required) {
|
|
||||||
Option option = new Option(opt, property.desc());
|
|
||||||
option.setArgs(args);
|
|
||||||
option.setLongOpt(property.prop());
|
|
||||||
option.setRequired(required);
|
|
||||||
return option;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.MkvToolNix;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public class FileUtils {
|
||||||
|
private static final boolean isWindows = SystemUtils.IS_OS_WINDOWS;
|
||||||
|
|
||||||
|
private static String expandPath(File dir, MkvToolNix application) {
|
||||||
|
return dir.getAbsolutePath().endsWith("/")
|
||||||
|
? dir.getAbsolutePath() + application
|
||||||
|
: dir.getAbsolutePath() + "/" + application;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static File getPathFor(File dir, MkvToolNix application) {
|
||||||
|
return Path.of(expandPath(dir, application) + (isWindows ? ".exe" : "")).toFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ConfigErrors;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@@ -28,10 +26,4 @@ public class LanguageValidatorUtil {
|
|||||||
public static boolean isLanguageValid(String language) {
|
public static boolean isLanguageValid(String language) {
|
||||||
return ISO3_LANGUAGES.contains(language);
|
return ISO3_LANGUAGES.contains(language);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void isLanguageValid(String language, ConfigErrors errors) {
|
|
||||||
if (!isLanguageValid(language)) {
|
|
||||||
errors.add(String.format("%s is not a valid language", language));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.MkvToolNix;
|
||||||
|
import org.apache.logging.log4j.util.Strings;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
public class ProjectUtil implements CommandLine.IVersionProvider {
|
||||||
|
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 getProjectName() {
|
||||||
|
return PROJECT_PROPERTIES.getProperty("project_name");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getVersion() throws IOException {
|
||||||
|
String mkvpropeeditVersion = getVersion(MkvToolNix.MKV_PROP_EDIT);
|
||||||
|
String mkvmergeVersion = getVersion(MkvToolNix.MKV_MERGE);
|
||||||
|
|
||||||
|
return new String[] {
|
||||||
|
getProjectName() + " " + PROJECT_PROPERTIES.getProperty("version"),
|
||||||
|
"Java ${java.version} (${java.vendor} ${java.vm.name} ${java.vm.version})",
|
||||||
|
"${os.name} ${os.version} ${os.arch}",
|
||||||
|
(!Strings.isBlank(mkvpropeeditVersion) ? mkvpropeeditVersion : "MkvPropEdit not found") + ", " + (!Strings.isBlank(mkvmergeVersion) ? mkvmergeVersion : "MkvMerge not found")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getVersion(MkvToolNix app) throws IOException {
|
||||||
|
ProcessBuilder processBuilder = new ProcessBuilder(app.toString(), "--version");
|
||||||
|
Process process = processBuilder.start();
|
||||||
|
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
|
||||||
|
String version = reader.readLine();
|
||||||
|
int exitCode = process.waitFor();
|
||||||
|
|
||||||
|
if (exitCode == 0) return version;
|
||||||
|
} catch (IOException | InterruptedException ignored) {}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
import jakarta.validation.Validation;
|
||||||
|
import jakarta.validation.Validator;
|
||||||
|
import jakarta.validation.ValidatorFactory;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public class ValidationUtil {
|
||||||
|
private static final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
|
||||||
|
@Getter
|
||||||
|
private static final Validator validator = factory.getValidator();
|
||||||
|
}
|
||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
src/main/resources/log4j2-debian.yaml
Normal file
34
src/main/resources/log4j2-debian.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
Configuration:
|
||||||
|
name: DefaultLogger
|
||||||
|
Appenders:
|
||||||
|
RollingFile:
|
||||||
|
name: FileAppender
|
||||||
|
fileName: ${sys:user.home}/.local/mkvaudiosubtitlechanger/logs/application.log
|
||||||
|
filePattern: ${sys:user.home}/.local/mkvaudiosubtitlechanger/logs/archive/application-%d{yyyy-MM-dd}-%i.log.gz
|
||||||
|
PatternLayout:
|
||||||
|
Pattern: "%d{DEFAULT} | %-5level | %thread | %C{1} | %msg %n %throwable"
|
||||||
|
ThresholdFilter:
|
||||||
|
level: debug
|
||||||
|
Policies:
|
||||||
|
OnStartupTriggeringPolicy:
|
||||||
|
minSize: 0
|
||||||
|
DefaultRolloverStrategy:
|
||||||
|
max: 30
|
||||||
|
Delete:
|
||||||
|
basePath: archive
|
||||||
|
maxDepth: 1
|
||||||
|
IfLastModified:
|
||||||
|
age: 30d
|
||||||
|
IfAccumulatedFileSize:
|
||||||
|
exceeds: 1GB
|
||||||
|
|
||||||
|
Loggers:
|
||||||
|
Root:
|
||||||
|
level: info
|
||||||
|
AppenderRef:
|
||||||
|
- ref: FileAppender
|
||||||
|
Logger:
|
||||||
|
name: "com.zaxxer.hikari.HikariConfig"
|
||||||
|
level: info
|
||||||
|
AppenderRef:
|
||||||
|
- ref: FileAppender
|
||||||
42
src/main/resources/log4j2-dev.yaml
Normal file
42
src/main/resources/log4j2-dev.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
Configuration:
|
||||||
|
name: DefaultLogger
|
||||||
|
Appenders:
|
||||||
|
Console:
|
||||||
|
name: Console_Out
|
||||||
|
PatternLayout:
|
||||||
|
Pattern: "%d{DEFAULT} | %-5level | %thread | %C{1} | %msg %n %throwable"
|
||||||
|
ThresholdFilter:
|
||||||
|
level: debug
|
||||||
|
RollingFile:
|
||||||
|
name: FileAppender
|
||||||
|
fileName: logs/application.log
|
||||||
|
filePattern: logs/archive/application-%d{yyyy-MM-dd}-%i.log.gz
|
||||||
|
PatternLayout:
|
||||||
|
Pattern: "%d{DEFAULT} | %-5level | %thread | %C{1} | %msg %n %throwable"
|
||||||
|
ThresholdFilter:
|
||||||
|
level: debug
|
||||||
|
Policies:
|
||||||
|
OnStartupTriggeringPolicy:
|
||||||
|
minSize: 0
|
||||||
|
DefaultRolloverStrategy:
|
||||||
|
max: 30
|
||||||
|
Delete:
|
||||||
|
basePath: logs/archive
|
||||||
|
maxDepth: 1
|
||||||
|
IfLastModified:
|
||||||
|
age: 30d
|
||||||
|
IfAccumulatedFileSize:
|
||||||
|
exceeds: 1GB
|
||||||
|
|
||||||
|
Loggers:
|
||||||
|
Root:
|
||||||
|
level: debug
|
||||||
|
AppenderRef:
|
||||||
|
- ref: Console_Out
|
||||||
|
- ref: FileAppender
|
||||||
|
Logger:
|
||||||
|
name: "com.zaxxer.hikari.HikariConfig"
|
||||||
|
level: info
|
||||||
|
AppenderRef:
|
||||||
|
- ref: Console_Out
|
||||||
|
- ref: FileAppender
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
Configuration:
|
|
||||||
name: DefaultLogger
|
|
||||||
Appenders:
|
|
||||||
Console:
|
|
||||||
name: Console_Out
|
|
||||||
PatternLayout:
|
|
||||||
Pattern: "%d{DEFAULT} | %-5level | %thread | %msg %n %throwable"
|
|
||||||
ThresholdFilter:
|
|
||||||
level: debug
|
|
||||||
File:
|
|
||||||
name: FileAppender
|
|
||||||
fileName: default.log
|
|
||||||
PatternLayout:
|
|
||||||
Pattern: "%d{DEFAULT} | %-5level | %thread | %msg %n %throwable"
|
|
||||||
ThresholdFilter:
|
|
||||||
level: debug
|
|
||||||
Loggers:
|
|
||||||
Root:
|
|
||||||
level: debug
|
|
||||||
AppenderRef:
|
|
||||||
- ref: Console_Out
|
|
||||||
- ref: FileAppender
|
|
||||||
Logger:
|
|
||||||
name: "com.zaxxer.hikari.HikariConfig"
|
|
||||||
level: info
|
|
||||||
AppenderRef:
|
|
||||||
- ref: Console_Out
|
|
||||||
- ref: FileAppender
|
|
||||||
34
src/main/resources/log4j2-windows.yaml
Normal file
34
src/main/resources/log4j2-windows.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
Configuration:
|
||||||
|
name: DefaultLogger
|
||||||
|
Appenders:
|
||||||
|
RollingFile:
|
||||||
|
name: FileAppender
|
||||||
|
fileName: ${sys:user.home}/AppData/Local/MKVAudioSubtitleChanger/logs/application.log
|
||||||
|
filePattern: ${sys:user.home}/AppData/Local/MKVAudioSubtitleChanger/logs/archive/application-%d{yyyy-MM-dd}-%i.log.gz
|
||||||
|
PatternLayout:
|
||||||
|
Pattern: "%d{DEFAULT} | %-5level | %thread | %C{1} | %msg %n %throwable"
|
||||||
|
ThresholdFilter:
|
||||||
|
level: debug
|
||||||
|
Policies:
|
||||||
|
OnStartupTriggeringPolicy:
|
||||||
|
minSize: 0
|
||||||
|
DefaultRolloverStrategy:
|
||||||
|
max: 30
|
||||||
|
Delete:
|
||||||
|
basePath: logs/archive
|
||||||
|
maxDepth: 1
|
||||||
|
IfLastModified:
|
||||||
|
age: 30d
|
||||||
|
IfAccumulatedFileSize:
|
||||||
|
exceeds: 1GB
|
||||||
|
|
||||||
|
Loggers:
|
||||||
|
Root:
|
||||||
|
level: info
|
||||||
|
AppenderRef:
|
||||||
|
- ref: FileAppender
|
||||||
|
Logger:
|
||||||
|
name: "com.zaxxer.hikari.HikariConfig"
|
||||||
|
level: info
|
||||||
|
AppenderRef:
|
||||||
|
- ref: FileAppender
|
||||||
@@ -1,13 +1,27 @@
|
|||||||
Configuration:
|
Configuration:
|
||||||
name: DefaultLogger
|
name: DefaultLogger
|
||||||
Appenders:
|
Appenders:
|
||||||
File:
|
RollingFile:
|
||||||
name: FileAppender
|
name: FileAppender
|
||||||
fileName: default.log
|
fileName: logs/application.log
|
||||||
|
filePattern: logs/archive/application-%d{yyyy-MM-dd}-%i.log.gz
|
||||||
PatternLayout:
|
PatternLayout:
|
||||||
Pattern: "%d{DEFAULT} | %-5level | %thread | %msg %n %throwable"
|
Pattern: "%d{DEFAULT} | %-5level | %thread | %C{1} | %msg %n %throwable"
|
||||||
ThresholdFilter:
|
ThresholdFilter:
|
||||||
level: info
|
level: debug
|
||||||
|
Policies:
|
||||||
|
OnStartupTriggeringPolicy:
|
||||||
|
minSize: 0
|
||||||
|
DefaultRolloverStrategy:
|
||||||
|
max: 30
|
||||||
|
Delete:
|
||||||
|
basePath: logs/archive
|
||||||
|
maxDepth: 1
|
||||||
|
IfLastModified:
|
||||||
|
age: 30d
|
||||||
|
IfAccumulatedFileSize:
|
||||||
|
exceeds: 1GB
|
||||||
|
|
||||||
Loggers:
|
Loggers:
|
||||||
Root:
|
Root:
|
||||||
level: info
|
level: info
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config.converter;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.converter.AttributeConfigConverter;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class AttributeConfigConverterTest {
|
||||||
|
|
||||||
|
private static Stream<Arguments> validData() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("jpn:ger", new AttributeConfig("jpn", "ger")),
|
||||||
|
Arguments.of("eng:eng", new AttributeConfig("eng", "eng")),
|
||||||
|
Arguments.of("OFF:OFF", new AttributeConfig("OFF", "OFF"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("validData")
|
||||||
|
void convert(String input, AttributeConfig expected) {
|
||||||
|
AttributeConfigConverter underTest = new AttributeConfigConverter();
|
||||||
|
AttributeConfig actual = underTest.convert(input);
|
||||||
|
assertEquals(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> invalidData() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("ars:eng"),
|
||||||
|
Arguments.of("ars:OFF"),
|
||||||
|
Arguments.of("OFF:ars"),
|
||||||
|
Arguments.of("ars:ars"),
|
||||||
|
Arguments.of("arss:ars"),
|
||||||
|
Arguments.of("ars:arsr")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("invalidData")
|
||||||
|
void convertInvalid(String input) {
|
||||||
|
AttributeConfigConverter underTest = new AttributeConfigConverter();
|
||||||
|
assertThrows(CommandLine.TypeConversionException.class, () -> underTest.convert(input));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.InputConfig;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ResultStatistic;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.DateUtils;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockedStatic;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.PathUtils.TEST_FILE;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class FileFilterTest {
|
||||||
|
@Mock(strictness = Mock.Strictness.LENIENT)
|
||||||
|
File file;
|
||||||
|
|
||||||
|
@Mock(strictness = Mock.Strictness.LENIENT)
|
||||||
|
BasicFileAttributes attributes;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void beforeEach() {
|
||||||
|
ResultStatistic.getInstance(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> accept() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of("~/video.mkv", Set.of(".mkv"), Set.of(), -1, ".*", true, false),
|
||||||
|
Arguments.of("~/video.mp4", Set.of(".mkv"), Set.of(), -1, ".*", false, false),
|
||||||
|
|
||||||
|
Arguments.of("~/video.mkv", Set.of(".mkv"), Set.of(), -1, "v.*", true, false),
|
||||||
|
Arguments.of("~/video.mkv", Set.of(".mkv"), Set.of(), -1, "a.*", false, true),
|
||||||
|
|
||||||
|
Arguments.of("~/video.mkv", Set.of(".mkv"), Set.of(), -1000, ".*", true, false),
|
||||||
|
Arguments.of("~/video.mkv", Set.of(".mkv"), Set.of(), 1000, ".*", false, true),
|
||||||
|
|
||||||
|
Arguments.of("dir/video.mkv", Set.of(".mkv"), Set.of("dir"), -1, ".*", false, true),
|
||||||
|
Arguments.of("dir/dir2/video.mkv", Set.of(".mkv"), Set.of("dir/dir2"), -1, ".*", false, true),
|
||||||
|
Arguments.of("dir/video.mkv", Set.of(".mkv"), Set.of("dir/dir2"), -1, ".*", true, false),
|
||||||
|
Arguments.of("dirr/video.mkv", Set.of(".mkv"), Set.of("dir"), -1, ".*", true, false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param filterDateOffset move filter data into the future or past by positive and negative values
|
||||||
|
*/
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource
|
||||||
|
void accept(String path, Set<String> extensions, Set<String> excludedDirs, int filterDateOffset, String pattern, boolean acceptanceExpected, boolean excluded) {
|
||||||
|
when(file.getAbsolutePath()).thenReturn(path);
|
||||||
|
when(file.getPath()).thenReturn(path);
|
||||||
|
String[] split = path.split("/");
|
||||||
|
when(file.getName()).thenReturn(split[split.length - 1]);
|
||||||
|
when(file.toPath()).thenReturn(Path.of(TEST_FILE));
|
||||||
|
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
FileFilter fileFilter = new FileFilter(excludedDirs, Pattern.compile(pattern), new Date(currentTime + filterDateOffset));
|
||||||
|
|
||||||
|
try (MockedStatic<DateUtils> mockedFiles = Mockito.mockStatic(DateUtils.class)) {
|
||||||
|
mockedFiles
|
||||||
|
.when(() -> DateUtils.convert(anyLong()))
|
||||||
|
.thenReturn(new Date(currentTime));
|
||||||
|
|
||||||
|
assertEquals(acceptanceExpected, fileFilter.accept(file, new HashSet<>(extensions)), "File is accepted");
|
||||||
|
assertEquals(excluded, ResultStatistic.getInstance().getExcluded() > 0, "Is counted in excluded statistic");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackAttributes;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackType;
|
||||||
|
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.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<TrackAttributes> input, List<TrackAttributes> expected) {
|
||||||
|
assertIterableEquals(expected, input.stream().sorted(comparator.reversed()).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TrackAttributes attr(String trackName, boolean defaultTrack) {
|
||||||
|
return new TrackAttributes(0, "", trackName, defaultTrack, false, false, false, TrackType.SUBTITLES);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfo;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackAttributes;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.FileInfoTestUtil.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class AttributeChangeProcessorTest {
|
||||||
|
|
||||||
|
private static Stream<Arguments> attributeConfigMatching() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(
|
||||||
|
List.of(withName(AUDIO_ENG, null), SUB_ENG),
|
||||||
|
arr(a("eng:eng")), "eng:eng",
|
||||||
|
Map.ofEntries(on(withName(AUDIO_ENG, null)), on(SUB_ENG))
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG, SUB_ENG),
|
||||||
|
arr(a("eng:eng")), "eng:eng",
|
||||||
|
Map.ofEntries(on(AUDIO_ENG), on(SUB_ENG))
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG, AUDIO_GER, SUB_ENG, SUB_GER),
|
||||||
|
arr(a("eng:eng")), "eng:eng",
|
||||||
|
Map.ofEntries(on(AUDIO_ENG), on(SUB_ENG))
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG_DEFAULT, AUDIO_GER, SUB_ENG, SUB_GER),
|
||||||
|
arr(a("ger:eng")), "ger:eng",
|
||||||
|
Map.ofEntries(off(AUDIO_ENG_DEFAULT), on(AUDIO_GER), on(SUB_ENG))
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG_DEFAULT, AUDIO_GER, SUB_ENG, SUB_GER),
|
||||||
|
arr(a("eng:ger")), "eng:ger",
|
||||||
|
Map.ofEntries(on(SUB_GER))
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG_DEFAULT, AUDIO_GER, SUB_ENG_DEFAULT, SUB_GER),
|
||||||
|
arr(a("eng:OFF")), "eng:OFF",
|
||||||
|
Map.ofEntries(off(SUB_ENG_DEFAULT))
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG_DEFAULT, AUDIO_GER, SUB_ENG_DEFAULT, SUB_GER),
|
||||||
|
arr(a("OFF:OFF")), "OFF:OFF",
|
||||||
|
Map.ofEntries(off(AUDIO_ENG_DEFAULT), off(SUB_ENG_DEFAULT))
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG_DEFAULT, AUDIO_GER, SUB_GER),
|
||||||
|
arr(a("eng:eng"), a("eng:ger")), "eng:ger",
|
||||||
|
Map.ofEntries(on(SUB_GER))
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG_DEFAULT, AUDIO_GER_COMMENTARY, SUB_GER_FORCED),
|
||||||
|
arr(a("ger:ger")), null,
|
||||||
|
Map.ofEntries()
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG_DEFAULT, AUDIO_GER_COMMENTARY, withName(SUB_GER, "forced")),
|
||||||
|
arr(a("ger:ger")), null,
|
||||||
|
Map.ofEntries()
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG_DEFAULT, AUDIO_GER_HEARING, SUB_GER),
|
||||||
|
arr(a("ger:ger")), null,
|
||||||
|
Map.ofEntries()
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG_DEFAULT, withName(AUDIO_GER, "SDH"), SUB_GER),
|
||||||
|
arr(a("ger:ger")), null,
|
||||||
|
Map.ofEntries()
|
||||||
|
),
|
||||||
|
Arguments.of(
|
||||||
|
List.of(AUDIO_ENG_DEFAULT, AUDIO_GER_COMMENTARY, AUDIO_GER_HEARING, AUDIO_GER, SUB_GER_FORCED, SUB_GER),
|
||||||
|
arr(a("ger:ger")), "ger:ger",
|
||||||
|
Map.ofEntries(off(AUDIO_ENG_DEFAULT), on(AUDIO_GER), on(SUB_GER))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("attributeConfigMatching")
|
||||||
|
void findDefaultMatchAndApplyChanges(List<TrackAttributes> tracks, AttributeConfig[] config, String expectedConfig, Map<TrackAttributes, Boolean> changes) {
|
||||||
|
AttributeChangeProcessor attributeChangeProcessor = new AttributeChangeProcessor(new String[]{}, Set.of("forced"), Set.of("commentary"), Set.of("SDH"));
|
||||||
|
|
||||||
|
FileInfo fileInfo = new FileInfo(null);
|
||||||
|
fileInfo.addTracks(tracks);
|
||||||
|
attributeChangeProcessor.findDefaultMatchAndApplyChanges(fileInfo, config);
|
||||||
|
assertEquals(expectedConfig, fileInfo.getMatchedConfig() != null ? fileInfo.getMatchedConfig().toStringShort() : fileInfo.getMatchedConfig());
|
||||||
|
assertEquals(changes.size(), fileInfo.getChanges().getDefaultTrack().size());
|
||||||
|
changes.forEach((key, value) -> {
|
||||||
|
assertTrue(fileInfo.getChanges().getDefaultTrack().containsKey(key));
|
||||||
|
assertEquals(value, fileInfo.getChanges().getDefaultTrack().get(key));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AttributeConfig[] arr(AttributeConfig... configs) {
|
||||||
|
return configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AttributeConfig a(String config) {
|
||||||
|
String[] split = config.split(":");
|
||||||
|
return new AttributeConfig(split[0], split[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map.Entry<TrackAttributes, Boolean> on(TrackAttributes track) {
|
||||||
|
return Map.entry(track, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map.Entry<TrackAttributes, Boolean> off(TrackAttributes track) {
|
||||||
|
return Map.entry(track, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> filterForPossibleDefaults() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(List.of(AUDIO_GER, AUDIO_ENG, SUB_GER), Set.of(AUDIO_GER, AUDIO_ENG, SUB_GER)),
|
||||||
|
Arguments.of(List.of(AUDIO_GER, AUDIO_ENG, withName(AUDIO_GER, "forced"), SUB_GER), Set.of(AUDIO_GER, AUDIO_ENG, SUB_GER)),
|
||||||
|
Arguments.of(List.of(AUDIO_GER, AUDIO_ENG, withName(AUDIO_GER, "Forced"), SUB_GER), Set.of(AUDIO_GER, AUDIO_ENG, SUB_GER)),
|
||||||
|
Arguments.of(List.of(AUDIO_GER, AUDIO_ENG, withName(AUDIO_GER, "commentary"), SUB_GER), Set.of(AUDIO_GER, AUDIO_ENG, SUB_GER)),
|
||||||
|
Arguments.of(List.of(AUDIO_GER, AUDIO_ENG, withName(AUDIO_GER, "Commentary"), SUB_GER), Set.of(AUDIO_GER, AUDIO_ENG, SUB_GER)),
|
||||||
|
Arguments.of(List.of(AUDIO_GER, AUDIO_ENG, withName(AUDIO_GER, "SDH"), SUB_GER), Set.of(AUDIO_GER, AUDIO_ENG, SUB_GER)),
|
||||||
|
Arguments.of(List.of(AUDIO_GER, AUDIO_ENG, withName(AUDIO_GER, "sdh"), SUB_GER), Set.of(AUDIO_GER, AUDIO_ENG, SUB_GER)),
|
||||||
|
Arguments.of(List.of(AUDIO_GER, AUDIO_ENG, SUB_GER, SUB_GER_FORCED, AUDIO_GER_COMMENTARY, AUDIO_GER_HEARING), Set.of(AUDIO_GER, AUDIO_ENG, SUB_GER))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("filterForPossibleDefaults")
|
||||||
|
void filterForPossibleDefaults(List<TrackAttributes> tracks, Set<TrackAttributes> expected) throws InvocationTargetException, IllegalAccessException {
|
||||||
|
AttributeChangeProcessor attributeChangeProcessor = new AttributeChangeProcessor(new String[]{}, Set.of("forced"), Set.of("commentary"), Set.of("SDH"));
|
||||||
|
Optional<Method> method = Arrays.stream(AttributeChangeProcessor.class.getDeclaredMethods())
|
||||||
|
.filter(m -> m.getName().equals("filterForPossibleDefaults"))
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
assertTrue(method.isPresent());
|
||||||
|
Method underTest = method.get();
|
||||||
|
underTest.setAccessible(true);
|
||||||
|
List<TrackAttributes> result = (List<TrackAttributes>) underTest.invoke(attributeChangeProcessor, tracks);
|
||||||
|
assertEquals(expected.size(), result.size());
|
||||||
|
for (TrackAttributes track : result) {
|
||||||
|
assertTrue(expected.contains(track));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> findForcedTracksAndApplyChanges() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(List.of(),
|
||||||
|
Set.of("song & signs"), false,
|
||||||
|
Map.ofEntries()
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(withName(SUB_GER, "song & signs"), SUB_GER),
|
||||||
|
Set.of("song & signs"), false,
|
||||||
|
Map.ofEntries(on(withName(SUB_GER, "song & signs")))
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(withName(SUB_GER, "song & signs"), withName(SUB_GER, "")),
|
||||||
|
Set.of("song & signs"), false,
|
||||||
|
Map.ofEntries(on(withName(SUB_GER, "song & signs")))
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(withName(SUB_GER, "song & signs"), withName(SUB_GER, null)),
|
||||||
|
Set.of("song & signs"), false,
|
||||||
|
Map.ofEntries(on(withName(SUB_GER, "song & signs")))
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(withName(SUB_GER, "song & signs")),
|
||||||
|
Set.of("song & signs"), false,
|
||||||
|
Map.ofEntries(on(withName(SUB_GER, "song & signs")))
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(withName(SUB_GER_FORCED, "song & signs")),
|
||||||
|
Set.of("song & signs"), false,
|
||||||
|
Map.ofEntries()
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(SUB_GER_FORCED, withName(SUB_GER, "song & signs")),
|
||||||
|
Set.of("song & signs"), true,
|
||||||
|
Map.ofEntries(off(SUB_GER_FORCED), on(withName(SUB_GER, "song & signs")))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("findForcedTracksAndApplyChanges")
|
||||||
|
void findForcedTracksAndApplyChanges(List<TrackAttributes> tracks, Set<String> keywords, boolean overwrite, Map<TrackAttributes, Boolean> changes) {
|
||||||
|
AttributeChangeProcessor attributeChangeProcessor = new AttributeChangeProcessor(new String[]{}, keywords, Set.of(), Set.of());
|
||||||
|
|
||||||
|
FileInfo fileInfo = new FileInfo(null);
|
||||||
|
fileInfo.addTracks(tracks);
|
||||||
|
attributeChangeProcessor.findForcedTracksAndApplyChanges(fileInfo, overwrite);
|
||||||
|
|
||||||
|
assertEquals(changes.size(), fileInfo.getChanges().getForcedTrack().size());
|
||||||
|
changes.forEach((key, value) -> {
|
||||||
|
assertTrue(fileInfo.getChanges().getForcedTrack().containsKey(key));
|
||||||
|
assertEquals(value, fileInfo.getChanges().getForcedTrack().get(key));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> findCommentaryTracksAndApplyChanges() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(List.of(withName(SUB_GER, "commentary"), withName(SUB_GER, null)),
|
||||||
|
Set.of("commentary"),
|
||||||
|
Map.ofEntries(on(withName(SUB_GER, "commentary")))
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(withName(SUB_GER, "commentary")),
|
||||||
|
Set.of("commentary"),
|
||||||
|
Map.ofEntries(on(withName(SUB_GER, "commentary")))
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(withName(AUDIO_GER_COMMENTARY, "commentary")),
|
||||||
|
Set.of("commentary"),
|
||||||
|
Map.ofEntries()
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(AUDIO_GER_COMMENTARY, withName(SUB_GER, "commentary")),
|
||||||
|
Set.of("commentary"),
|
||||||
|
Map.ofEntries(on(withName(SUB_GER, "commentary")))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("findCommentaryTracksAndApplyChanges")
|
||||||
|
void findCommentaryTracksAndApplyChanges(List<TrackAttributes> tracks, Set<String> keywords, Map<TrackAttributes, Boolean> changes) {
|
||||||
|
AttributeChangeProcessor attributeChangeProcessor = new AttributeChangeProcessor(new String[]{}, Set.of(), keywords, Set.of());
|
||||||
|
|
||||||
|
FileInfo fileInfo = new FileInfo(null);
|
||||||
|
fileInfo.addTracks(tracks);
|
||||||
|
attributeChangeProcessor.findCommentaryTracksAndApplyChanges(fileInfo);
|
||||||
|
|
||||||
|
assertEquals(changes.size(), fileInfo.getChanges().getCommentaryTrack().size());
|
||||||
|
changes.forEach((key, value) -> {
|
||||||
|
assertTrue(fileInfo.getChanges().getCommentaryTrack().containsKey(key));
|
||||||
|
assertEquals(value, fileInfo.getChanges().getCommentaryTrack().get(key));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> findHearingImpairedTracksAndApplyChanges() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(List.of(withName(SUB_GER, "SDH"), withName(SUB_GER, null)),
|
||||||
|
Set.of("SDH"),
|
||||||
|
Map.ofEntries(on(withName(SUB_GER, "SDH")))
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(withName(SUB_GER, "SDH")),
|
||||||
|
Set.of("SDH"),
|
||||||
|
Map.ofEntries(on(withName(SUB_GER, "SDH")))
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(withName(AUDIO_GER_HEARING, "SDH")),
|
||||||
|
Set.of("SDH"),
|
||||||
|
Map.ofEntries()
|
||||||
|
),
|
||||||
|
Arguments.of(List.of(AUDIO_GER_HEARING, withName(SUB_GER, "SDH")),
|
||||||
|
Set.of("SDH"),
|
||||||
|
Map.ofEntries(on(withName(SUB_GER, "SDH")))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("findHearingImpairedTracksAndApplyChanges")
|
||||||
|
void findHearingImpairedTracksAndApplyChanges(List<TrackAttributes> tracks, Set<String> keywords, Map<TrackAttributes, Boolean> changes) {
|
||||||
|
AttributeChangeProcessor attributeChangeProcessor = new AttributeChangeProcessor(new String[]{}, Set.of(), Set.of(), keywords);
|
||||||
|
|
||||||
|
FileInfo fileInfo = new FileInfo(null);
|
||||||
|
fileInfo.addTracks(tracks);
|
||||||
|
attributeChangeProcessor.findHearingImpairedTracksAndApplyChanges(fileInfo);
|
||||||
|
|
||||||
|
assertEquals(changes.size(), fileInfo.getChanges().getHearingImpairedTrack().size());
|
||||||
|
changes.forEach((key, value) -> {
|
||||||
|
assertTrue(fileInfo.getChanges().getHearingImpairedTrack().containsKey(key));
|
||||||
|
assertEquals(value, fileInfo.getChanges().getHearingImpairedTrack().get(key));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.*;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.FileInfoTestUtil.AUDIO_GER;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class AttributeUpdaterTest {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setup() {
|
||||||
|
ResultStatistic.getInstance(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> checkStatusAndUpdate() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(info(new AttributeConfig("ger", "ger"), AUDIO_GER), supplier(() -> ResultStatistic.getInstance().getChangePlanned())),
|
||||||
|
Arguments.of(info(new AttributeConfig("ger", "ger"), null), supplier(() -> ResultStatistic.getInstance().getUnchanged())),
|
||||||
|
Arguments.of(info(null, null), supplier(() -> ResultStatistic.getInstance().getUnchanged()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("checkStatusAndUpdate")
|
||||||
|
void checkStatusAndUpdate(FileInfo fileInfo, Supplier<Integer> getActual) {
|
||||||
|
InputConfig config = new InputConfig();
|
||||||
|
config.setThreads(1);
|
||||||
|
config.setSafeMode(true);
|
||||||
|
AttributeUpdater underTest = new AttributeUpdater(config, null, null) {
|
||||||
|
@Override
|
||||||
|
protected List<File> getFiles() {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void process(File file) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
underTest.checkStatusAndUpdate(fileInfo);
|
||||||
|
assertEquals(1, getActual.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Supplier<Integer> supplier(Supplier<Integer> supplier) {
|
||||||
|
return supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileInfo info(AttributeConfig config, TrackAttributes attr) {
|
||||||
|
FileInfo fileInfo = new FileInfo(null);
|
||||||
|
fileInfo.setMatchedConfig(config);
|
||||||
|
if(attr != null) fileInfo.getChanges().getDefaultTrack().put(attr, true);
|
||||||
|
return fileInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.CommandRunner;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfo;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.InputConfig;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackAttributes;
|
||||||
|
import org.apache.commons.lang3.tuple.Pair;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.FileInfoTestUtil.*;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class CoherentAttributeUpdaterTest {
|
||||||
|
@Mock(lenient = true)
|
||||||
|
FileProcessor fileProcessor;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void process() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> findMatch() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(AttributeConfig.of("ger", "ger"),
|
||||||
|
List.of(), false, 0),
|
||||||
|
Arguments.of(AttributeConfig.of("ger", "ger"),
|
||||||
|
List.of(fileInfoMock("test.mkv", AUDIO_GER, SUB_GER)), true, 1),
|
||||||
|
Arguments.of(AttributeConfig.of("ger", "ger"),
|
||||||
|
List.of(fileInfoMock("test.mkv", AUDIO_GER, SUB_GER),
|
||||||
|
fileInfoMock("test2.mkv", AUDIO_GER, SUB_GER)), true, 2),
|
||||||
|
Arguments.of(AttributeConfig.of("ger", "ger"),
|
||||||
|
List.of(fileInfoMock("test.mkv", AUDIO_GER, SUB_GER),
|
||||||
|
fileInfoMock("test2.mkv", AUDIO_ENG, SUB_ENG)), false, 1),
|
||||||
|
Arguments.of(AttributeConfig.of("ger", "ger"),
|
||||||
|
List.of(fileInfoMock("test.mkv", AUDIO_GER, SUB_GER),
|
||||||
|
fileInfoMock("test2.mkv", AUDIO_GER, SUB_GER),
|
||||||
|
fileInfoMock("test3.mkv", AUDIO_GER, SUB_GER),
|
||||||
|
fileInfoMock("test4.mkv", AUDIO_GER, SUB_GER),
|
||||||
|
fileInfoMock("test5.mkv", AUDIO_ENG, SUB_ENG)), false, 4),
|
||||||
|
Arguments.of(AttributeConfig.of("ger", "ger"),
|
||||||
|
List.of(fileInfoMock("test.mkv", AUDIO_GER, SUB_GER),
|
||||||
|
fileInfoMock("test2.mkv", AUDIO_ENG, SUB_GER),
|
||||||
|
fileInfoMock("test3.mkv", AUDIO_GER, SUB_GER),
|
||||||
|
fileInfoMock("test4.mkv", AUDIO_GER, SUB_GER),
|
||||||
|
fileInfoMock("test5.mkv", AUDIO_GER, SUB_ENG)), false, 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("findMatch")
|
||||||
|
void findMatch(AttributeConfig attributeConfig, List<Pair<File, FileInfo>> fileInfoMock, boolean expectedMatch, int expectedMatchCount) throws InvocationTargetException, IllegalAccessException {
|
||||||
|
CommandRunner commandRunner = new CommandRunner();
|
||||||
|
new CommandLine(commandRunner).parseArgs("-a", "ger:ger", "/arst");
|
||||||
|
InputConfig config = commandRunner.getConfig();
|
||||||
|
AttributeChangeProcessor attributeChangeProcessor = new AttributeChangeProcessor(config.getPreferredSubtitles().toArray(new String[0]), config.getForcedKeywords(), config.getCommentaryKeywords(), config.getHearingImpaired());
|
||||||
|
CoherentAttributeUpdater updater = new CoherentAttributeUpdater(config, fileProcessor, attributeChangeProcessor);
|
||||||
|
Set<FileInfo> matchedFiles = new HashSet<>(fileInfoMock.size() * 2);
|
||||||
|
|
||||||
|
List<File> files = new ArrayList<>();
|
||||||
|
for (Pair<File, FileInfo> pair : fileInfoMock) {
|
||||||
|
when(fileProcessor.readAttributes(pair.getKey())).thenReturn(pair.getRight());
|
||||||
|
files.add(pair.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
Method underTest = Arrays.stream(updater.getClass().getDeclaredMethods()).filter(m -> "findMatch".equals(m.getName())).findFirst().get();
|
||||||
|
underTest.setAccessible(true);
|
||||||
|
AttributeConfig actualMatch = (AttributeConfig) underTest.invoke(updater, attributeConfig, matchedFiles, files);
|
||||||
|
|
||||||
|
assertEquals(expectedMatch ? attributeConfig : null, actualMatch, "Matched AttributeConfig");
|
||||||
|
assertEquals(expectedMatchCount, matchedFiles.size(), "Matched files count");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pair<File, FileInfo> fileInfoMock(String path, TrackAttributes... tracks) {
|
||||||
|
File file = new File(path);
|
||||||
|
FileInfo fileInfo = new FileInfo(file);
|
||||||
|
fileInfo.addTracks(List.of(tracks));
|
||||||
|
return Pair.of(file, fileInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileInfo;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackAttributes;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackType;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class MkvFileProcessorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void readAttributes() throws IOException {
|
||||||
|
String mkvmergeResponse = """
|
||||||
|
{
|
||||||
|
"tracks": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"properties": {
|
||||||
|
"default_track": true,
|
||||||
|
"enabled_track": true,
|
||||||
|
"forced_track": false,
|
||||||
|
"language": "jpn",
|
||||||
|
"number": 1
|
||||||
|
},
|
||||||
|
"type": "video"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"properties": {
|
||||||
|
"track_name": "testing",
|
||||||
|
"default_track": true,
|
||||||
|
"enabled_track": true,
|
||||||
|
"forced_track": false,
|
||||||
|
"language": "jpn",
|
||||||
|
"number": 2
|
||||||
|
},
|
||||||
|
"type": "audio"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"properties": {
|
||||||
|
"default_track": true,
|
||||||
|
"enabled_track": true,
|
||||||
|
"forced_track": false,
|
||||||
|
"commentary_track": true,
|
||||||
|
"flag_hearing_impaired": true,
|
||||||
|
"language": "eng",
|
||||||
|
"number": 3
|
||||||
|
},
|
||||||
|
"type": "subtitles"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
MkvFileProcessor underTest = spy(new MkvFileProcessor(new File("mkvtoolnix"), null));
|
||||||
|
doReturn(new ByteArrayInputStream(mkvmergeResponse.getBytes(StandardCharsets.UTF_8)))
|
||||||
|
.when(underTest).run(any(String[].class));
|
||||||
|
|
||||||
|
FileInfo result = underTest.readAttributes(new File("arst"));
|
||||||
|
|
||||||
|
TrackAttributes audio = result.getAudioTracks().get(0);
|
||||||
|
assertEquals(2, audio.id());
|
||||||
|
assertEquals("testing", audio.trackName());
|
||||||
|
assertEquals("jpn", audio.language());
|
||||||
|
assertTrue(audio.defaultt());
|
||||||
|
assertFalse(audio.forced());
|
||||||
|
assertFalse(audio.hearingImpaired());
|
||||||
|
assertFalse(audio.commentary());
|
||||||
|
assertEquals(TrackType.AUDIO, audio.type());
|
||||||
|
|
||||||
|
TrackAttributes sub = result.getSubtitleTracks().get(0);
|
||||||
|
assertEquals(3, sub.id());
|
||||||
|
assertNull(sub.trackName());
|
||||||
|
assertEquals("eng", sub.language());
|
||||||
|
assertTrue(sub.defaultt());
|
||||||
|
assertFalse(sub.forced());
|
||||||
|
assertTrue(sub.hearingImpaired());
|
||||||
|
assertTrue(sub.commentary());
|
||||||
|
assertEquals(TrackType.SUBTITLES, sub.type());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void getUpdateCommand() throws InvocationTargetException, IllegalAccessException {
|
||||||
|
FileInfo fileInfo = new FileInfo(new File("./"));
|
||||||
|
fileInfo.getChanges().getDefaultTrack().put(t(1), true);
|
||||||
|
fileInfo.getChanges().getDefaultTrack().put(t(2), false);
|
||||||
|
fileInfo.getChanges().getForcedTrack().put(t(3), true);
|
||||||
|
fileInfo.getChanges().getForcedTrack().put(t(4), false);
|
||||||
|
fileInfo.getChanges().getCommentaryTrack().put(t(5), true);
|
||||||
|
fileInfo.getChanges().getCommentaryTrack().put(t(6), false);
|
||||||
|
fileInfo.getChanges().getHearingImpairedTrack().put(t(7), true);
|
||||||
|
fileInfo.getChanges().getHearingImpairedTrack().put(t(8), false);
|
||||||
|
String[] expectedCommand = """
|
||||||
|
--edit track:1 --set flag-default=1
|
||||||
|
--edit track:2 --set flag-default=0
|
||||||
|
--edit track:3 --set flag-forced=1
|
||||||
|
--edit track:4 --set flag-forced=0
|
||||||
|
--edit track:5 --set flag-commentary=1
|
||||||
|
--edit track:6 --set flag-commentary=0
|
||||||
|
--edit track:7 --set flag-hearing-impaired=1
|
||||||
|
--edit track:8 --set flag-hearing-impaired=0
|
||||||
|
""".split("\\n");
|
||||||
|
|
||||||
|
MkvFileProcessor mkvFileProcessor = new MkvFileProcessor(new File("mkvtoolnix"), null);
|
||||||
|
Method underTest = Arrays.stream(mkvFileProcessor.getClass().getDeclaredMethods()).filter(m -> "getUpdateCommand".equals(m.getName())).findFirst().get();
|
||||||
|
underTest.setAccessible(true);
|
||||||
|
String[] actualCommand = (String[]) underTest.invoke(mkvFileProcessor, fileInfo);
|
||||||
|
String[] trimmedActualCommand = Arrays.copyOfRange(actualCommand, 2, actualCommand.length);
|
||||||
|
String actualCommandString = String.join(" ", trimmedActualCommand);
|
||||||
|
assertTrue(expectedCommand.length * 4 == trimmedActualCommand.length, "Command length is equal");
|
||||||
|
for (String commandPart: expectedCommand) {
|
||||||
|
assertTrue(actualCommandString.contains(commandPart));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TrackAttributes t(int id) {
|
||||||
|
return new TrackAttributes(id, "", "", false, false, false, false, TrackType.AUDIO);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.CommandRunner;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.Arguments;
|
||||||
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.io.StringWriter;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.PathUtils.*;
|
||||||
|
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.TestUtil.args;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class ValidationExecutionStrategyTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validate() {
|
||||||
|
CommandRunner underTest = new CommandRunner();
|
||||||
|
new CommandLine(underTest)
|
||||||
|
.setExecutionStrategy(new ValidationExecutionStrategy())
|
||||||
|
.parseArgs("-a", "ger:ger", "-m", TEST_MKVTOOLNIX_DIR, TEST_FILE);
|
||||||
|
|
||||||
|
assertEquals(TEST_FILE, underTest.getConfig().getLibraryPath().getPath().replace("\\", "/"));
|
||||||
|
assertEquals(TEST_MKVTOOLNIX_DIR, underTest.getConfig().getMkvToolNix().getPath().replace("\\", "/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Stream<Arguments> validateFailure() {
|
||||||
|
return Stream.of(
|
||||||
|
Arguments.of(new String[]{"-a", "jpn:ger"}, "Error: Missing required argument(s): <libraryPath>"),
|
||||||
|
Arguments.of(new String[]{"/arstarstarst"}, "libraryPath does not exist"),
|
||||||
|
Arguments.of(new String[]{"/arstarstarst", "-a",}, "Missing required parameter for option '--attribute-config' at index 0 (<attributeConfig>)"),
|
||||||
|
Arguments.of(new String[]{"/arstarstarst", "-a", "jpn:ger"}, "libraryPath does not exist"),
|
||||||
|
Arguments.of(new String[]{"/arstarstarst", "-m"}, "Missing required parameter for option '--mkvtoolnix' (<mkvToolNix>)"),
|
||||||
|
Arguments.of(new String[]{"./", "-m", TEST_INVALID_DIR}, "mkvToolNix does not exist"),
|
||||||
|
Arguments.of(new String[]{"./", "-t"}, "Missing required parameter for option '--threads' (<threads>)"),
|
||||||
|
Arguments.of(new String[]{"./", "-t", "0"}, "threads must be greater than or equal to 1"),
|
||||||
|
Arguments.of(new String[]{"./", "-t", "-1"}, "threads must be greater than or equal to 1"),
|
||||||
|
Arguments.of(new String[]{"./", "-c", "-1"}, "coherent must be greater than or equal to 0")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("validateFailure")
|
||||||
|
void validateFailure(String[] args, String expectedMessage) {
|
||||||
|
StringWriter writer = new StringWriter();
|
||||||
|
PrintWriter printWriter = new PrintWriter(writer);
|
||||||
|
|
||||||
|
new CommandLine(CommandRunner.class)
|
||||||
|
.setExecutionStrategy(new ValidationExecutionStrategy())
|
||||||
|
.setErr(printWriter)
|
||||||
|
.execute(args);
|
||||||
|
|
||||||
|
printWriter.flush();
|
||||||
|
assertEquals(expectedMessage, writer.toString().split("[\r\n]")[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
@Disabled
|
||||||
|
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,28 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackAttributes;
|
||||||
|
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackType;
|
||||||
|
|
||||||
|
public class FileInfoTestUtil {
|
||||||
|
public static final TrackAttributes AUDIO_GER = new TrackAttributes(0, "ger", "", false, false, false, false, TrackType.AUDIO);
|
||||||
|
public static final TrackAttributes AUDIO_ENG = new TrackAttributes(1, "eng", "", false, false, false, false, TrackType.AUDIO);
|
||||||
|
public static final TrackAttributes AUDIO_GER_DEFAULT = new TrackAttributes(0, "ger", "", true, false, false, false, TrackType.AUDIO);
|
||||||
|
public static final TrackAttributes AUDIO_ENG_DEFAULT = new TrackAttributes(1, "eng", "", true, false, false, false, TrackType.AUDIO);
|
||||||
|
public static final TrackAttributes AUDIO_GER_FORCED = new TrackAttributes(0, "ger", "", false, true, false, false, TrackType.AUDIO);
|
||||||
|
public static final TrackAttributes AUDIO_ENG_FORCED = new TrackAttributes(1, "eng", "", false, true, false, false, TrackType.AUDIO);
|
||||||
|
public static final TrackAttributes AUDIO_GER_COMMENTARY = new TrackAttributes(0, "ger", "", false, false, true, false, TrackType.AUDIO);
|
||||||
|
public static final TrackAttributes AUDIO_ENG_COMMENTARY = new TrackAttributes(1, "eng", "", false, false, true, false, TrackType.AUDIO);
|
||||||
|
public static final TrackAttributes AUDIO_GER_HEARING = new TrackAttributes(0, "ger", "", false, false, false, true, TrackType.AUDIO);
|
||||||
|
public static final TrackAttributes AUDIO_ENG_HEARING = new TrackAttributes(1, "ger", "", false, false, false, true, TrackType.AUDIO);
|
||||||
|
|
||||||
|
public static final TrackAttributes SUB_GER = new TrackAttributes(0, "ger", "", false, false, false, false, TrackType.SUBTITLES);
|
||||||
|
public static final TrackAttributes SUB_ENG = new TrackAttributes(1, "eng", "", false, false, false, false, TrackType.SUBTITLES);
|
||||||
|
public static final TrackAttributes SUB_GER_DEFAULT = new TrackAttributes(0, "ger", "", true, false, false, false, TrackType.SUBTITLES);
|
||||||
|
public static final TrackAttributes SUB_ENG_DEFAULT = new TrackAttributes(1, "eng", "", true, false, false, false, TrackType.SUBTITLES);
|
||||||
|
public static final TrackAttributes SUB_GER_FORCED = new TrackAttributes(0, "ger", "", false, true, false, false, TrackType.SUBTITLES);
|
||||||
|
public static final TrackAttributes SUB_ENG_FORCED = new TrackAttributes(1, "eng", "", false, true, false, false, TrackType.SUBTITLES);
|
||||||
|
|
||||||
|
public static TrackAttributes withName(TrackAttributes track, String trackName) {
|
||||||
|
return new TrackAttributes(track.id(), track.language(), trackName, track.defaultt(), track.forced(), track.commentary(), track.hearingImpaired(), track.type());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
|
||||||
|
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";
|
||||||
|
public static final String TEST_INVALID_DIR = "src/test/resources/test-dir";
|
||||||
|
public static final String TEST_MKVTOOLNIX_DIR = SystemUtils.IS_OS_WINDOWS ? "src/test/resources/mkvtoolnix_exe" : "src/test/resources/mkvtoolnix";
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||||
|
|
||||||
|
public class TestUtil {
|
||||||
|
|
||||||
|
public static String[] args(String... args) {
|
||||||
|
String[] staticArray = new String[]{"-a", "jpn:ger", "/"};
|
||||||
|
String[] result = new String[staticArray.length + args.length];
|
||||||
|
System.arraycopy(args, 0, result, 0, args.length);
|
||||||
|
System.arraycopy(staticArray, 0, result, args.length, staticArray.length);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/test/resources/mkvtoolnix_exe/mkvmerge.exe
Normal file
0
src/test/resources/mkvtoolnix_exe/mkvmerge.exe
Normal file
0
src/test/resources/mkvtoolnix_exe/mkvpropedit.exe
Normal file
0
src/test/resources/mkvtoolnix_exe/mkvpropedit.exe
Normal file
0
src/test/resources/test-dir/test-file.mkv
Normal file
0
src/test/resources/test-dir/test-file.mkv
Normal file
144
src/wix/resources/main.wxs
Normal file
144
src/wix/resources/main.wxs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
|
||||||
|
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
|
||||||
|
|
||||||
|
<?ifdef JpIsSystemWide ?>
|
||||||
|
<?define JpInstallScope="perMachine"?>
|
||||||
|
<?else?>
|
||||||
|
<?define JpInstallScope="perUser"?>
|
||||||
|
<?endif?>
|
||||||
|
|
||||||
|
<?define JpProductLanguage=1033 ?>
|
||||||
|
<?define JpInstallerVersion=200 ?>
|
||||||
|
<?define JpCompressedMsi=yes ?>
|
||||||
|
|
||||||
|
<?ifdef JpAllowUpgrades ?>
|
||||||
|
<?define JpUpgradeVersionOnlyDetectUpgrade="no"?>
|
||||||
|
<?else?>
|
||||||
|
<?define JpUpgradeVersionOnlyDetectUpgrade="yes"?>
|
||||||
|
<?endif?>
|
||||||
|
<?ifdef JpAllowDowngrades ?>
|
||||||
|
<?define JpUpgradeVersionOnlyDetectDowngrade="no"?>
|
||||||
|
<?else?>
|
||||||
|
<?define JpUpgradeVersionOnlyDetectDowngrade="yes"?>
|
||||||
|
<?endif?>
|
||||||
|
|
||||||
|
<?define JpProductCode="*"?>
|
||||||
|
<?define JpAppName="${project.artifactId}"?>
|
||||||
|
<?define JpAppVersion="${project.version}"?>
|
||||||
|
<?define JpAppVendor="${project.maintainer}"?>
|
||||||
|
<?define JpProductUpgradeCode="a9527300-d364-4cc3-a392-94035065d8c9"?>
|
||||||
|
<?define JpAppDescription="${project.description}"?>
|
||||||
|
<?define JpHelpURL="github.com/${project.maintainer}/${project.artifactId}"?>
|
||||||
|
|
||||||
|
<Product
|
||||||
|
Id="$(var.JpProductCode)"
|
||||||
|
Name="$(var.JpAppName)"
|
||||||
|
Language="$(var.JpProductLanguage)"
|
||||||
|
Version="$(var.JpAppVersion)"
|
||||||
|
Manufacturer="$(var.JpAppVendor)"
|
||||||
|
UpgradeCode="$(var.JpProductUpgradeCode)">
|
||||||
|
|
||||||
|
<Package
|
||||||
|
Description="$(var.JpAppDescription)"
|
||||||
|
Manufacturer="$(var.JpAppVendor)"
|
||||||
|
InstallerVersion="$(var.JpInstallerVersion)"
|
||||||
|
Compressed="$(var.JpCompressedMsi)"
|
||||||
|
InstallScope="$(var.JpInstallScope)" Platform="x64"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Media Id="1" Cabinet="Data.cab" EmbedCab="yes" />
|
||||||
|
|
||||||
|
<Upgrade Id="$(var.JpProductUpgradeCode)">
|
||||||
|
<UpgradeVersion
|
||||||
|
OnlyDetect="$(var.JpUpgradeVersionOnlyDetectUpgrade)"
|
||||||
|
Property="JP_UPGRADABLE_FOUND"
|
||||||
|
Maximum="$(var.JpAppVersion)"
|
||||||
|
MigrateFeatures="yes"
|
||||||
|
IncludeMaximum="$(var.JpUpgradeVersionOnlyDetectUpgrade)" />
|
||||||
|
<UpgradeVersion
|
||||||
|
OnlyDetect="$(var.JpUpgradeVersionOnlyDetectDowngrade)"
|
||||||
|
Property="JP_DOWNGRADABLE_FOUND"
|
||||||
|
Minimum="$(var.JpAppVersion)"
|
||||||
|
MigrateFeatures="yes"
|
||||||
|
IncludeMinimum="$(var.JpUpgradeVersionOnlyDetectDowngrade)" />
|
||||||
|
</Upgrade>
|
||||||
|
|
||||||
|
<?ifndef JpAllowUpgrades ?>
|
||||||
|
<CustomAction Id="JpDisallowUpgrade" Error="!(loc.DisallowUpgradeErrorMessage)" />
|
||||||
|
<?endif?>
|
||||||
|
<?ifndef JpAllowDowngrades ?>
|
||||||
|
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
|
||||||
|
<?endif?>
|
||||||
|
|
||||||
|
<Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
|
||||||
|
|
||||||
|
<CustomAction Id="JpFindRelatedProducts" BinaryKey="JpCaDll" DllEntry="FindRelatedProductsEx" />
|
||||||
|
|
||||||
|
<!-- Standard required root -->
|
||||||
|
<Directory Id="TARGETDIR" Name="SourceDir"/>
|
||||||
|
|
||||||
|
<Feature Id="DefaultFeature" Title="!(loc.MainFeatureTitle)" Level="1">
|
||||||
|
<ComponentGroupRef Id="Shortcuts"/>
|
||||||
|
<ComponentGroupRef Id="Files"/>
|
||||||
|
<ComponentGroupRef Id="FileAssociations"/>
|
||||||
|
<Component Id="pathEnvironmentVariable" Guid="$(var.JpProductUpgradeCode)" KeyPath="yes" Directory="TARGETDIR">
|
||||||
|
<Environment Id="MyPathVariable" Name="Path" Value="[INSTALLDIR]" Action="set" System="no" Permanent="no" Part="last" Separator=";" />
|
||||||
|
</Component>
|
||||||
|
</Feature>
|
||||||
|
|
||||||
|
<CustomAction Id="JpSetARPINSTALLLOCATION" Property="ARPINSTALLLOCATION" Value="[INSTALLDIR]" />
|
||||||
|
<CustomAction Id="JpSetARPCOMMENTS" Property="ARPCOMMENTS" Value="$(var.JpAppDescription)" />
|
||||||
|
<CustomAction Id="JpSetARPCONTACT" Property="ARPCONTACT" Value="$(var.JpAppVendor)" />
|
||||||
|
<!-- <CustomAction Id="JpSetARPSIZE" Property="ARPSIZE" Value="$(var.JpAppSizeKb)" /> -->
|
||||||
|
|
||||||
|
<?ifdef JpHelpURL ?>
|
||||||
|
<CustomAction Id="JpSetARPHELPLINK" Property="ARPHELPLINK" Value="$(var.JpHelpURL)" />
|
||||||
|
<?endif?>
|
||||||
|
|
||||||
|
<?ifdef JpAboutURL ?>
|
||||||
|
<CustomAction Id="JpSetARPURLINFOABOUT" Property="ARPURLINFOABOUT" Value="$(var.JpAboutURL)" />
|
||||||
|
<?endif?>
|
||||||
|
|
||||||
|
<?ifdef JpUpdateURL ?>
|
||||||
|
<CustomAction Id="JpSetARPURLUPDATEINFO" Property="ARPURLUPDATEINFO" Value="$(var.JpUpdateURL)" />
|
||||||
|
<?endif?>
|
||||||
|
|
||||||
|
<?ifdef JpIcon ?>
|
||||||
|
<Property Id="ARPPRODUCTICON" Value="JpARPPRODUCTICON"/>
|
||||||
|
<Icon Id="JpARPPRODUCTICON" SourceFile="$(var.JpIcon)"/>
|
||||||
|
<?endif?>
|
||||||
|
|
||||||
|
<UIRef Id="JpUI"/>
|
||||||
|
|
||||||
|
<InstallExecuteSequence>
|
||||||
|
<Custom Action="JpSetARPINSTALLLOCATION" After="CostFinalize">Not Installed</Custom>
|
||||||
|
<Custom Action="JpSetARPCOMMENTS" After="CostFinalize">Not Installed</Custom>
|
||||||
|
<Custom Action="JpSetARPCONTACT" After="CostFinalize">Not Installed</Custom>
|
||||||
|
<!-- <Custom Action="JpSetARPSIZE" After="CostFinalize">Not Installed</Custom> -->
|
||||||
|
<?ifdef JpHelpURL ?>
|
||||||
|
<Custom Action="JpSetARPHELPLINK" After="CostFinalize">Not Installed</Custom>
|
||||||
|
<?endif?>
|
||||||
|
<?ifdef JpAboutURL ?>
|
||||||
|
<Custom Action="JpSetARPURLINFOABOUT" After="CostFinalize">Not Installed</Custom>
|
||||||
|
<?endif?>
|
||||||
|
<?ifdef JpUpdateURL ?>
|
||||||
|
<Custom Action="JpSetARPURLUPDATEINFO" After="CostFinalize">Not Installed</Custom>
|
||||||
|
<?endif?>
|
||||||
|
|
||||||
|
<?ifndef JpAllowUpgrades ?>
|
||||||
|
<Custom Action="JpDisallowUpgrade" After="JpFindRelatedProducts">JP_UPGRADABLE_FOUND</Custom>
|
||||||
|
<?endif?>
|
||||||
|
<?ifndef JpAllowDowngrades ?>
|
||||||
|
<Custom Action="JpDisallowDowngrade" After="JpFindRelatedProducts">JP_DOWNGRADABLE_FOUND</Custom>
|
||||||
|
<?endif?>
|
||||||
|
<RemoveExistingProducts Before="CostInitialize"/>
|
||||||
|
<Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/>
|
||||||
|
</InstallExecuteSequence>
|
||||||
|
|
||||||
|
<InstallUISequence>
|
||||||
|
<Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/>
|
||||||
|
</InstallUISequence>
|
||||||
|
|
||||||
|
</Product>
|
||||||
|
</Wix>
|
||||||
@@ -1 +0,0 @@
|
|||||||
version=${project.version}
|
|
||||||
Reference in New Issue
Block a user