mirror of
https://github.com/RatzzFatzz/MKVAudioSubtitleChanger.git
synced 2026-02-11 02:05:56 +01:00
Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e80331beef | ||
|
|
a5fce22b95 | ||
|
|
a97ed89d08 | ||
|
|
cce84f5c15 | ||
|
|
ff38457af1 | ||
|
|
906ec944eb | ||
|
|
41e107ef85 | ||
|
|
3c57eb44ef | ||
|
|
8dbfb22ed8 | ||
|
|
a5aae0acf4 | ||
|
|
1165dd8380 | ||
|
|
be004e6146 | ||
|
|
31b155d8a3 | ||
|
|
94365651ff | ||
|
|
f18fdbdda6 | ||
|
|
13283e3765 | ||
|
|
949261fb17 | ||
|
|
6cf42e5915 | ||
|
|
842b97dcb6 | ||
|
|
c2af135a57 | ||
|
|
280771e545 | ||
|
|
7620771aed | ||
|
|
c7670e36c1 | ||
|
|
1e31326ea2 | ||
|
|
7230134de6 | ||
|
|
fa84c483d9 | ||
|
|
62b637c241 | ||
|
|
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 | ||
| 10954f07a4 | |||
| a99abd5989 | |||
| 1b62e151a5 | |||
| 7140528441 | |||
| f70d1649a0 | |||
| bfb6cab62c | |||
| a606c50637 | |||
| c2f32a30ce | |||
| f9a7bd1af6 | |||
| 5cbf3fede2 | |||
|
|
0ee92cfd7f | ||
| 5d86b43388 | |||
| d637558cfe | |||
| 4e53d99c25 | |||
|
|
a19fbac03a | ||
| a5b0224d6c | |||
| f7a2e4234a | |||
| 93f3542cf1 | |||
|
|
b94f6eb7de | ||
| c11431d85b | |||
| 68e5b9e988 | |||
| a1e9031cbc | |||
| 1df06e8a08 | |||
| feaeda746a | |||
| f2fb296698 | |||
| 03efab657c |
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"
|
||||
23
.github/ISSUE_TEMPLATE/default-bug-report.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/default-bug-report.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Create a report to help me improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: RatzzFatzz
|
||||
|
||||
---
|
||||
|
||||
**Environment**
|
||||
Java Version: [e.g. 8, 11, 17]
|
||||
MKV Audio Subtitle Changer version: [e.g. v1.1, v2.0]
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
### Please attach logs
|
||||
11
.github/ISSUE_TEMPLATE/default-feature-request.md
vendored
Normal file
11
.github/ISSUE_TEMPLATE/default-feature-request.md
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Tell me about something you would want me to add
|
||||
title: ''
|
||||
labels: feature-request
|
||||
|
||||
---
|
||||
|
||||
**Describe your feature**
|
||||
A clear and concise description of the expected behaviour.
|
||||
|
||||
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
|
||||
logs/
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
674
LICENSE
Normal file
674
LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
110
README.md
110
README.md
@@ -1,46 +1,78 @@
|
||||
### Table of content
|
||||
- Introduction
|
||||
- Requirements
|
||||
- Running
|
||||
- config.yml example
|
||||
## Introduction
|
||||
|
||||
### Introduction
|
||||
This CLI tool uses MKVToolNix to quickly modify track properties in MKV files without reencoding. Use profiles to set default audio/subtitle tracks and add commentary, hearing impaired, and forced flags in bulk.
|
||||

|
||||
|
||||
This program helps changing audio and subtitle lines of mkv files.
|
||||
## Requirements
|
||||
|
||||
### Requirements
|
||||
|
||||
- Java 8 or higher
|
||||
- Java 21 or newer
|
||||
- mkvtoolnix installation
|
||||
|
||||
### Running
|
||||
|
||||
Opening terminal / cmd in the directory of the jar and the config file and execute following command:
|
||||
|
||||
`java -jar mkvaudiosubtitleschanger.jar [path to mkv or dir with mkv]`
|
||||
|
||||
You have to replace the brackets and the content of it with the path to your mkv file or the directory with mkv files.
|
||||
|
||||
### config.yml example
|
||||
Config file needs to be placed in the same directory as the jar.
|
||||
|
||||
## Execution
|
||||
```shell
|
||||
# Portable
|
||||
java -jar mkvaudiosubtitlechanger.jar --attribute-config eng:ger -s ./videos
|
||||
# Installed
|
||||
mkvaudiosubtitlechanger.jar --attribute-config eng:ger -s ./videos
|
||||
```
|
||||
mkvtoolnixPath: /usr/bin
|
||||
config:
|
||||
1:
|
||||
audio:
|
||||
- jpn
|
||||
subtitle:
|
||||
- ger
|
||||
- eng
|
||||
2:
|
||||
audio:
|
||||
- ger
|
||||
- eng
|
||||
subtitle:
|
||||
- OFF
|
||||
Remove `--safemode` or `-s` to actually apply the changes. Using safemode for the first execution is recommended.
|
||||
|
||||
### Update defaults
|
||||
To update the default flag for tracks use `--attribute-config` or `-a`.
|
||||
This parameter takes in a list of pairs `audio:subtitle` (E.g. `eng:ger`).
|
||||
The order of these configs matters, because they are processed in order.
|
||||
The matching stops when the first match was found or when no match was found.
|
||||
For example `-a ger:OFF eng:ger` first tries to find a match for german audio, if that is not possible it tries the same for english with german subs.
|
||||
This can be extended indefinitely.
|
||||
|
||||
Using this parameter is not required, but it is the reason I originally started developing this tool.
|
||||
|
||||
### Available parameters
|
||||
```
|
||||
-a, --attribute-config=<attributeConfig>...
|
||||
List of audio:subtitle pairs for matching defaults in order (e.g. jpn:eng jpn:ger)
|
||||
-m, --mkvtoolnix=<mkvToolNix>
|
||||
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. Attempting increasing deeper levels until match is found (worst case applying config on single file basis)
|
||||
-cf, --force-coherent only applies changes if a coherent match was found for the specifically entered depth
|
||||
-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
|
||||
-e, --exclude=<excluded>...
|
||||
relative 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, CC
|
||||
--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
|
||||
```
|
||||
This config will first check if there is japanese audio and german or english subtitles available, if yes,
|
||||
it will set these attributes. If these are not available, it will check the second part. This means, it checks
|
||||
if german or english audio is available. It does not care for the subtitle, because it's "off", which means, it
|
||||
will disable subtitles in this case.
|
||||
|
||||
14
config.yaml
14
config.yaml
@@ -1,14 +0,0 @@
|
||||
mkvtoolnixPath: /usr/bin
|
||||
config:
|
||||
1:
|
||||
audio:
|
||||
- jpn
|
||||
subtitle:
|
||||
- ger
|
||||
- eng
|
||||
2:
|
||||
audio:
|
||||
- ger
|
||||
- eng
|
||||
subtitle:
|
||||
- OFF
|
||||
BIN
example.gif
Normal file
BIN
example.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 145 KiB |
487
language-codes
Normal file
487
language-codes
Normal file
@@ -0,0 +1,487 @@
|
||||
aar
|
||||
abk
|
||||
ace
|
||||
ach
|
||||
ada
|
||||
ady
|
||||
afa
|
||||
afh
|
||||
afr
|
||||
ain
|
||||
aka
|
||||
akk
|
||||
alb
|
||||
ale
|
||||
alg
|
||||
alt
|
||||
ang
|
||||
amh
|
||||
anp
|
||||
apa
|
||||
ara
|
||||
arc
|
||||
arg
|
||||
arm
|
||||
arn
|
||||
arp
|
||||
art
|
||||
arw
|
||||
asm
|
||||
ast
|
||||
ath
|
||||
aus
|
||||
ava
|
||||
ave
|
||||
awa
|
||||
aym
|
||||
aze
|
||||
bad
|
||||
bai
|
||||
bak
|
||||
bal
|
||||
bam
|
||||
ban
|
||||
baq
|
||||
bas
|
||||
bat
|
||||
bej
|
||||
bel
|
||||
bem
|
||||
ben
|
||||
ber
|
||||
bho
|
||||
bih
|
||||
bik
|
||||
bin
|
||||
bis
|
||||
bla
|
||||
bnt
|
||||
bos
|
||||
bra
|
||||
bre
|
||||
btk
|
||||
bua
|
||||
bug
|
||||
bul
|
||||
bur
|
||||
byn
|
||||
cad
|
||||
cai
|
||||
car
|
||||
cat
|
||||
cau
|
||||
ceb
|
||||
cel
|
||||
cha
|
||||
chb
|
||||
che
|
||||
chg
|
||||
chi
|
||||
chk
|
||||
chm
|
||||
chn
|
||||
cho
|
||||
chp
|
||||
chr
|
||||
chu
|
||||
chv
|
||||
chy
|
||||
cmc
|
||||
cnr
|
||||
cop
|
||||
cor
|
||||
cos
|
||||
cpe
|
||||
cpf
|
||||
cpp
|
||||
cre
|
||||
crh
|
||||
crp
|
||||
csb
|
||||
cus
|
||||
cze
|
||||
dak
|
||||
dan
|
||||
dar
|
||||
day
|
||||
del
|
||||
den
|
||||
dgr
|
||||
din
|
||||
div
|
||||
doi
|
||||
dra
|
||||
dsb
|
||||
dua
|
||||
dum
|
||||
dut
|
||||
dyu
|
||||
dzo
|
||||
efi
|
||||
egy
|
||||
eka
|
||||
elx
|
||||
eng
|
||||
enm
|
||||
epo
|
||||
est
|
||||
ewe
|
||||
ewo
|
||||
fan
|
||||
fao
|
||||
fat
|
||||
fij
|
||||
fil
|
||||
fin
|
||||
fiu
|
||||
fon
|
||||
fre
|
||||
frm
|
||||
fro
|
||||
frr
|
||||
frs
|
||||
fry
|
||||
ful
|
||||
fur
|
||||
gaa
|
||||
gay
|
||||
gba
|
||||
gem
|
||||
geo
|
||||
ger
|
||||
gez
|
||||
gil
|
||||
gla
|
||||
gle
|
||||
glg
|
||||
glv
|
||||
gmh
|
||||
goh
|
||||
gon
|
||||
gor
|
||||
got
|
||||
grb
|
||||
grc
|
||||
gre
|
||||
grn
|
||||
gsw
|
||||
guj
|
||||
gwi
|
||||
hai
|
||||
hat
|
||||
hau
|
||||
haw
|
||||
heb
|
||||
her
|
||||
hil
|
||||
him
|
||||
hin
|
||||
hit
|
||||
hmn
|
||||
hmo
|
||||
hrv
|
||||
hsb
|
||||
hun
|
||||
hup
|
||||
iba
|
||||
ibo
|
||||
ice
|
||||
ido
|
||||
iii
|
||||
ijo
|
||||
iku
|
||||
ile
|
||||
ilo
|
||||
ina
|
||||
inc
|
||||
ind
|
||||
ine
|
||||
inh
|
||||
ipk
|
||||
ira
|
||||
iro
|
||||
ita
|
||||
jav
|
||||
jbo
|
||||
jpn
|
||||
jpr
|
||||
jrb
|
||||
kaa
|
||||
kab
|
||||
kac
|
||||
kal
|
||||
kam
|
||||
kan
|
||||
kar
|
||||
kas
|
||||
kau
|
||||
kaw
|
||||
kaz
|
||||
kbd
|
||||
kha
|
||||
khi
|
||||
khm
|
||||
kho
|
||||
kik
|
||||
kin
|
||||
kir
|
||||
kmb
|
||||
kok
|
||||
kom
|
||||
kon
|
||||
kor
|
||||
kos
|
||||
kpe
|
||||
krc
|
||||
krl
|
||||
kro
|
||||
kru
|
||||
kua
|
||||
kum
|
||||
kur
|
||||
kut
|
||||
lad
|
||||
lah
|
||||
lam
|
||||
lao
|
||||
lat
|
||||
lav
|
||||
lez
|
||||
lim
|
||||
lin
|
||||
lit
|
||||
lol
|
||||
loz
|
||||
ltz
|
||||
lua
|
||||
lub
|
||||
lug
|
||||
lui
|
||||
lun
|
||||
luo
|
||||
lus
|
||||
mac
|
||||
mad
|
||||
mag
|
||||
mah
|
||||
mai
|
||||
mak
|
||||
mal
|
||||
man
|
||||
mao
|
||||
map
|
||||
mar
|
||||
mas
|
||||
may
|
||||
mdf
|
||||
mdr
|
||||
men
|
||||
mga
|
||||
mic
|
||||
min
|
||||
mis
|
||||
mkh
|
||||
mlg
|
||||
mlt
|
||||
mnc
|
||||
mni
|
||||
mno
|
||||
moh
|
||||
mon
|
||||
mos
|
||||
mul
|
||||
mun
|
||||
mus
|
||||
mwl
|
||||
mwr
|
||||
myn
|
||||
myv
|
||||
nah
|
||||
nai
|
||||
nap
|
||||
nau
|
||||
nav
|
||||
nbl
|
||||
nde
|
||||
ndo
|
||||
nds
|
||||
nep
|
||||
new
|
||||
nia
|
||||
nic
|
||||
niu
|
||||
nno
|
||||
nob
|
||||
nog
|
||||
non
|
||||
nor
|
||||
nqo
|
||||
nso
|
||||
nub
|
||||
nwc
|
||||
nya
|
||||
nym
|
||||
nyn
|
||||
nyo
|
||||
nzi
|
||||
oci
|
||||
oji
|
||||
ori
|
||||
orm
|
||||
osa
|
||||
oss
|
||||
ota
|
||||
oto
|
||||
paa
|
||||
pag
|
||||
pal
|
||||
pam
|
||||
pan
|
||||
pap
|
||||
pau
|
||||
peo
|
||||
per
|
||||
phi
|
||||
phn
|
||||
pli
|
||||
pol
|
||||
pon
|
||||
por
|
||||
pra
|
||||
pro
|
||||
pus
|
||||
que
|
||||
raj
|
||||
rap
|
||||
rar
|
||||
roa
|
||||
roh
|
||||
rom
|
||||
rum
|
||||
run
|
||||
rup
|
||||
rus
|
||||
sad
|
||||
sag
|
||||
sah
|
||||
sai
|
||||
sal
|
||||
sam
|
||||
san
|
||||
sas
|
||||
sat
|
||||
scn
|
||||
sco
|
||||
sel
|
||||
sem
|
||||
sga
|
||||
sgn
|
||||
shn
|
||||
sid
|
||||
sin
|
||||
sio
|
||||
sit
|
||||
sla
|
||||
slo
|
||||
slv
|
||||
sma
|
||||
sme
|
||||
smi
|
||||
smj
|
||||
smn
|
||||
smo
|
||||
sms
|
||||
sna
|
||||
snd
|
||||
snk
|
||||
sog
|
||||
som
|
||||
son
|
||||
sot
|
||||
spa
|
||||
srd
|
||||
srn
|
||||
srp
|
||||
srr
|
||||
ssa
|
||||
ssw
|
||||
suk
|
||||
sun
|
||||
sus
|
||||
sux
|
||||
swa
|
||||
swe
|
||||
syc
|
||||
syr
|
||||
tah
|
||||
tai
|
||||
tam
|
||||
tat
|
||||
tel
|
||||
tem
|
||||
ter
|
||||
tet
|
||||
tgk
|
||||
tgl
|
||||
tha
|
||||
tib
|
||||
tig
|
||||
tir
|
||||
tiv
|
||||
tkl
|
||||
tlh
|
||||
tli
|
||||
tmh
|
||||
tog
|
||||
ton
|
||||
tpi
|
||||
tsi
|
||||
tsn
|
||||
tso
|
||||
tuk
|
||||
tum
|
||||
tup
|
||||
tur
|
||||
tut
|
||||
tvl
|
||||
twi
|
||||
tyv
|
||||
udm
|
||||
uga
|
||||
uig
|
||||
ukr
|
||||
umb
|
||||
und
|
||||
urd
|
||||
uzb
|
||||
vai
|
||||
ven
|
||||
vie
|
||||
vol
|
||||
vot
|
||||
wak
|
||||
wal
|
||||
war
|
||||
was
|
||||
wel
|
||||
wen
|
||||
wln
|
||||
wol
|
||||
xal
|
||||
xho
|
||||
yao
|
||||
yap
|
||||
yid
|
||||
yor
|
||||
ypk
|
||||
zap
|
||||
zbl
|
||||
zen
|
||||
zgh
|
||||
zha
|
||||
znd
|
||||
zul
|
||||
zun
|
||||
zxx
|
||||
zza
|
||||
OFF
|
||||
21
maven/assembly.xml
Normal file
21
maven/assembly.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
|
||||
<id>zip</id>
|
||||
<includeBaseDirectory>true</includeBaseDirectory>
|
||||
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
<format>tar</format>
|
||||
</formats>
|
||||
<files>
|
||||
<file>
|
||||
<source> ${project.build.directory}/${project.artifactId}-${project.version}.jar</source>
|
||||
<destName>${project.artifactId}.jar</destName>
|
||||
<outputDirectory>/</outputDirectory>
|
||||
</file>
|
||||
<file>
|
||||
<source></source>
|
||||
</file>
|
||||
</files>
|
||||
</assembly>
|
||||
416
pom.xml
416
pom.xml
@@ -4,9 +4,231 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>MKVAudioSubtilesChanger</groupId>
|
||||
<artifactId>MKVAudioSubtitlesChanger</artifactId>
|
||||
<version>1.0</version>
|
||||
<groupId>at.pcgamingfreaks</groupId>
|
||||
<artifactId>MKVAudioSubtitleChanger</artifactId>
|
||||
<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.yml</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.yml</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.yml</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.yml</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>
|
||||
<defaultGoal>clean package</defaultGoal>
|
||||
@@ -14,7 +236,13 @@
|
||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<directory>./</directory>
|
||||
<includes>
|
||||
<include>language-codes</include>
|
||||
<include>project.properties</include>
|
||||
<include>LICENSE</include>
|
||||
</includes>
|
||||
<filtering>true</filtering>
|
||||
</resource>
|
||||
</resources>
|
||||
<testResources>
|
||||
@@ -24,23 +252,14 @@
|
||||
</testResources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.1.1</version>
|
||||
<version>3.4.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Main-Class>at/pcgamingfreaks/mkvaudiosubtitlechanger/Main</Main-Class>
|
||||
<Main-Class>${mainClass}</Main-Class>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
@@ -48,7 +267,7 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<version>3.6.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
@@ -63,6 +282,13 @@
|
||||
<include>*:*</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<manifestEntries>
|
||||
<Multi-Release>true</Multi-Release>
|
||||
</manifestEntries>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
@@ -70,16 +296,33 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
<version>3.5.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>9</source>
|
||||
<target>9</target>
|
||||
<source>${java-version}</source>
|
||||
<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>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
@@ -91,86 +334,155 @@
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.intellij</groupId>
|
||||
<artifactId>forms_rt</artifactId>
|
||||
<version>7.0.3</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.8</version>
|
||||
<version>${lombok-version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/info.picocli/picocli -->
|
||||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
<version>4.7.7</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>4.0.0-M1</version>
|
||||
</dependency>
|
||||
<!-- Hibernate Validator -->
|
||||
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>9.1.0.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.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>jakarta.el</artifactId>
|
||||
<version>5.0.0-M1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- region logging -->
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<version>2.17.1</version>
|
||||
<version>2.25.3</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>2.17.1</version>
|
||||
<version>2.25.3</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j2-impl -->
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-slf4j-impl</artifactId>
|
||||
<version>2.17.1</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>
|
||||
<artifactId>log4j-slf4j2-impl</artifactId>
|
||||
<version>2.25.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-yaml</artifactId>
|
||||
<version>2.13.1</version>
|
||||
<version>2.20.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>2.13.1</version>
|
||||
<version>2.20.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/commons-cli/commons-cli -->
|
||||
<dependency>
|
||||
<groupId>commons-cli</groupId>
|
||||
<artifactId>commons-cli</artifactId>
|
||||
<version>1.11.0</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>3.20.0</version>
|
||||
</dependency>
|
||||
<!-- Source: https://mvnrepository.com/artifact/commons-io/commons-io -->
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.21.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/me.tongfei/progressbar -->
|
||||
<dependency>
|
||||
<groupId>me.tongfei</groupId>
|
||||
<artifactId>progressbar</artifactId>
|
||||
<version>0.10.1</version>
|
||||
</dependency>
|
||||
<!-- endregion -->
|
||||
<!-- region unit-tests -->
|
||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.4.2</version>
|
||||
<version>6.0.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.4.2</version>
|
||||
<version>6.0.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-all -->
|
||||
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-all</artifactId>
|
||||
<version>1.9.5</version>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>5.21.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>5.21.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.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- endregion -->
|
||||
<dependency>
|
||||
<groupId>at.pcgamingfreaks</groupId>
|
||||
<artifactId>YAML-Parser</artifactId>
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/net.harawata/appdirs -->
|
||||
<dependency>
|
||||
<groupId>net.harawata</groupId>
|
||||
<artifactId>appdirs</artifactId>
|
||||
<version>1.5.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</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.yml -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,45 +0,0 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.AttributeConfig;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.ConfigProcessor;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.intimpl.MkvFileCollector;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.util.ConfigUtil;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Log4j2
|
||||
public class AttributeUpdaterKernel {
|
||||
MkvFileCollector collector = new MkvFileCollector();
|
||||
|
||||
public void execute(String path) {
|
||||
List<AttributeConfig> configPattern = ConfigUtil.loadConfig();
|
||||
List<File> allValidPaths = collector.loadFiles(path);
|
||||
if(allValidPaths != null && configPattern != null){
|
||||
for(File file : allValidPaths){
|
||||
List<FileAttribute> attributes = collector.loadAttributes(file);
|
||||
if (attributes.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
boolean fileHasChanged = false;
|
||||
for(AttributeConfig config : configPattern){
|
||||
/*
|
||||
* Creating new ArrayList, because the method removes elements from the list by reference
|
||||
*/
|
||||
fileHasChanged = new ConfigProcessor(config).processConfig(file, new ArrayList<>(attributes));
|
||||
if(fileHasChanged){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(! fileHasChanged){
|
||||
log.info(file.getName() + " didn't change!");
|
||||
}
|
||||
}
|
||||
}else{
|
||||
log.error("Path is not valid or config has errors!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
@Log4j2
|
||||
@Getter
|
||||
@Setter
|
||||
public class MKVToolProperties {
|
||||
private String mkvmergePath;
|
||||
private String mkvpropeditPath;
|
||||
|
||||
private static MKVToolProperties instance = null;
|
||||
|
||||
private MKVToolProperties() {
|
||||
}
|
||||
|
||||
public static MKVToolProperties getInstance() {
|
||||
if(instance == null){
|
||||
instance = new MKVToolProperties();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,18 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger;
|
||||
|
||||
import at.pcgamingfreaks.yaml.YAML;
|
||||
import at.pcgamingfreaks.yaml.YamlInvalidContentException;
|
||||
import at.pcgamingfreaks.yaml.YamlKeyNotFoundException;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.CommandRunner;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation.ValidationExecutionStrategy;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@Log4j2
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
if(checkIfMKVToolNixIsValid()){
|
||||
AttributeUpdaterKernel kernel = new AttributeUpdaterKernel();
|
||||
kernel.execute(args[0]);
|
||||
}else{
|
||||
log.error("MKVToolNix was not found! Please recheck path");
|
||||
if (args.length == 0) {
|
||||
CommandLine.usage(CommandRunner.class, System.out);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkIfMKVToolNixIsValid() {
|
||||
try{
|
||||
String path = new YAML(new File("config.yaml")).getString("mkvtoolnixPath");
|
||||
if(! path.endsWith(File.separator)){
|
||||
path += File.separator;
|
||||
}
|
||||
if(System.getProperty("os.name").toLowerCase().contains("windows")){
|
||||
MKVToolProperties.getInstance().setMkvmergePath(path + "mkvmerge.exe");
|
||||
MKVToolProperties.getInstance().setMkvpropeditPath(path + "mkvpropedit.exe");
|
||||
}else{
|
||||
MKVToolProperties.getInstance().setMkvmergePath(path + "mkvmerge");
|
||||
MKVToolProperties.getInstance().setMkvpropeditPath(path + "mkvpropedit");
|
||||
}
|
||||
}catch(YamlKeyNotFoundException | IOException | YamlInvalidContentException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new File(MKVToolProperties.getInstance().getMkvmergePath()).isFile() && new File(MKVToolProperties.getInstance().getMkvpropeditPath()).isFile();
|
||||
new CommandLine(CommandRunner.class)
|
||||
.setExecutionStrategy(new ValidationExecutionStrategy())
|
||||
.execute(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Log4j2
|
||||
@Getter
|
||||
public class AttributeConfig {
|
||||
private List<String> audio;
|
||||
private List<String> subtitle;
|
||||
|
||||
public AttributeConfig(List<String> audio, List<String> subtitle) {
|
||||
this.audio = audio;
|
||||
this.subtitle = subtitle;
|
||||
}
|
||||
}
|
||||
@@ -1,194 +0,0 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.MKVToolProperties;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
@Log4j2
|
||||
public class ConfigProcessor {
|
||||
private int audioDefault = - 1;
|
||||
private int subtitleDefault = - 1;
|
||||
private final AttributeConfig config;
|
||||
|
||||
public ConfigProcessor(AttributeConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the config lists and apply the changes if the combination matches
|
||||
*
|
||||
* @param file is the file, which will be updated
|
||||
* @param attributes has the metadata for the transferred file
|
||||
* @return If the current configuration matched and changes applied or not
|
||||
*/
|
||||
public boolean processConfig(File file, List<FileAttribute> attributes) {
|
||||
// check if size is bigger or equal 2 to make sure that there is at least one audio and subtitle line
|
||||
// TODO: implement empty audio or subtitle line
|
||||
if(attributes.size() >= 2){
|
||||
TransferObject transfer = filterAttributes(attributes);
|
||||
if(! attributes.isEmpty()){
|
||||
return updateFile(file, attributes, transfer);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Filters the attributes that only those are remaining which are needed in the current configuration.
|
||||
* Also analyzes which tracks were default before.
|
||||
*/
|
||||
private TransferObject filterAttributes(List<FileAttribute> attributes) {
|
||||
TransferObject transfer = new TransferObject();
|
||||
Iterator<FileAttribute> iterator = attributes.iterator();
|
||||
while(iterator.hasNext()){
|
||||
FileAttribute elem = iterator.next();
|
||||
if("audio".equals(elem.getType())){
|
||||
if(elem.isDefaultTrack()){
|
||||
audioDefault = elem.getId();
|
||||
}
|
||||
if(config.getAudio().contains("OFF")){
|
||||
transfer.setAudioOn(false);
|
||||
transfer.setAudioIndex(- 2);
|
||||
}
|
||||
if(! config.getAudio().contains(elem.getLanguage())){
|
||||
iterator.remove();
|
||||
}
|
||||
}else if("subtitles".equals(elem.getType())){
|
||||
if(elem.isDefaultTrack()){
|
||||
subtitleDefault = elem.getId();
|
||||
}
|
||||
if(config.getSubtitle().contains("OFF")){
|
||||
transfer.setSubtitleOn(false);
|
||||
transfer.setSubtitleIndex(- 2);
|
||||
}
|
||||
if(! config.getSubtitle().contains(elem.getLanguage())){
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
return transfer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the command which will be executed if the attributes of the current file fit the current {@link AttributeConfig}
|
||||
*
|
||||
* @param file is the file, which will be updated
|
||||
* @param attributes has the metadata for the transferred file
|
||||
* @return if the the current file was updated or not. Returns true if the file already has the correct metadata set
|
||||
*/
|
||||
private boolean updateFile(File file, List<FileAttribute> attributes, TransferObject transfer) {
|
||||
StringBuilder stringBuffer = new StringBuilder();
|
||||
if(System.getProperty("os.name").toLowerCase().contains("windows")){
|
||||
stringBuffer.append("\"");
|
||||
stringBuffer.append(MKVToolProperties.getInstance().getMkvpropeditPath());
|
||||
stringBuffer.append("\" \"").append(file.getAbsolutePath()).append("\" ");
|
||||
}else{
|
||||
stringBuffer.append(MKVToolProperties.getInstance().getMkvpropeditPath());
|
||||
stringBuffer.append(" ").append(file.getAbsolutePath()).append(" ");
|
||||
}
|
||||
if(audioDefault != - 1){
|
||||
stringBuffer.append("--edit track:=").append(audioDefault).append(" --set flag-default=0 ");
|
||||
}
|
||||
if(subtitleDefault != - 1){
|
||||
stringBuffer.append("--edit track:=").append(subtitleDefault).append(" --set flag-default=0 ");
|
||||
}
|
||||
collectLines(attributes, transfer);
|
||||
if(transfer.isValid){
|
||||
if(transfer.isAudioOn){
|
||||
stringBuffer.append("--edit track:=").append(transfer.getAudioIndex()).append(" --set flag-default=1 ");
|
||||
}
|
||||
if(transfer.isSubtitleOn){
|
||||
stringBuffer.append("--edit track:=").append(transfer.getSubtitleIndex()).append(" --set flag-default=1 ");
|
||||
}
|
||||
if(subtitleDefault == transfer.getSubtitleIndex() && audioDefault == transfer.getAudioIndex()){
|
||||
/*
|
||||
* In this case the file would be change to the exact same audio and subtitle lines and we want to
|
||||
* avoid unnecessary changes to the file
|
||||
*/
|
||||
log.info(file.getName() + " already fits config!");
|
||||
return true;
|
||||
}
|
||||
try{
|
||||
Runtime.getRuntime().exec(stringBuffer.toString());
|
||||
}catch(IOException e){
|
||||
log.error("Couldn't make changes to file");
|
||||
}
|
||||
/*
|
||||
* We return true even if there was an error. If there was an error, the chances that this file is still
|
||||
* busy later.
|
||||
*/
|
||||
log.info(file.getName() + " was updated");
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the left over attributes and decides which is the most wanted for audio and subtitle
|
||||
*
|
||||
* @param attributes contains all the leftover attributes
|
||||
* @return is an object, which contains information about which audio and subtitle line is the best suitable for
|
||||
* the entered config. Also transfers a boolean which contains information about if the other two values
|
||||
* were set
|
||||
*/
|
||||
private TransferObject collectLines(List<FileAttribute> attributes, TransferObject transfer) {
|
||||
int subtitleListIndex = - 1;
|
||||
int audioListIndex = - 1;
|
||||
for(FileAttribute elem : attributes){
|
||||
if("audio".equals(elem.getType())){
|
||||
for(int i = 0; i < config.getAudio().size(); i++){
|
||||
audioListIndex = findIndex("audio", elem, audioListIndex, config.getAudio(), transfer);
|
||||
}
|
||||
}else if("subtitles".equals(elem.getType())){
|
||||
for(int i = 0; i < config.getSubtitle().size(); i++){
|
||||
subtitleListIndex = findIndex("subtitles", elem, subtitleListIndex, config.getSubtitle(), transfer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transfer.analyzeIfValid();
|
||||
return transfer;
|
||||
}
|
||||
|
||||
private int findIndex(String type, FileAttribute elem, int index, List<String> config, TransferObject transfer) {
|
||||
for(int i = 0; i < config.size(); i++){
|
||||
if(config.get(i).equals(elem.getLanguage()) && (index == - 1 || i < index)){
|
||||
switch(type){
|
||||
case "audio":
|
||||
transfer.setAudioIndex(elem.getId());
|
||||
break;
|
||||
case "subtitles":
|
||||
transfer.setSubtitleIndex(elem.getId());
|
||||
break;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
private static class TransferObject {
|
||||
private boolean isValid;
|
||||
private int audioIndex = - 1;
|
||||
private int subtitleIndex = - 1;
|
||||
private boolean isSubtitleOn = true;
|
||||
private boolean isAudioOn = true;
|
||||
|
||||
TransferObject() {
|
||||
}
|
||||
|
||||
private void analyzeIfValid() {
|
||||
isValid = audioIndex != - 1 && subtitleIndex != - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.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,73 @@
|
||||
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.LogManager;
|
||||
import org.apache.logging.log4j.core.LoggerContext;
|
||||
import org.apache.logging.log4j.core.appender.RollingFileAppender;
|
||||
import org.apache.logging.log4j.core.config.Configuration;
|
||||
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);
|
||||
}
|
||||
|
||||
if (config.isSafeMode()) {
|
||||
log.info("Safemode active. No files will be changed!");
|
||||
System.out.println("Safemode active. No files will be changed!");
|
||||
}
|
||||
|
||||
LastExecutionHandler lastExecutionHandler = config.isOnlyNewFiles() ? new LastExecutionHandler(getApplicationHome()) : null;
|
||||
FileFilter fileFilter = new FileFilter(config.getExcluded(), config.getIncludePattern(), config.getFilterDate(), lastExecutionHandler);
|
||||
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, lastExecutionHandler)
|
||||
: new SingleFileAttributeUpdater(config, fileProcessor, attributeChangeProcessor, lastExecutionHandler);
|
||||
kernel.execute();
|
||||
}
|
||||
|
||||
public String getApplicationHome() {
|
||||
LoggerContext context = (LoggerContext) LogManager.getContext(false);
|
||||
Configuration config = context.getConfiguration();
|
||||
|
||||
for (org.apache.logging.log4j.core.Appender appender : config.getAppenders().values()) {
|
||||
if (appender instanceof RollingFileAppender rollingFileAppender) {
|
||||
String fileName = rollingFileAppender.getFileName();
|
||||
return new java.io.File(fileName).getParentFile().getParent();
|
||||
}
|
||||
}
|
||||
|
||||
log.error("Could not load log4j2 path info");
|
||||
System.out.println("Could not load log4j2 path info");
|
||||
System.exit(1);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ResultStatistic;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class FileFilter {
|
||||
private final Set<String> excluded;
|
||||
private final Pattern includePattern;
|
||||
private final Instant filterDate;
|
||||
private final LastExecutionHandler lastExecutionHandler;
|
||||
|
||||
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)
|
||||
|| isExcluded(pathName, new HashSet<>(excluded))
|
||||
|| lastExecutionHandler != null && !isNewer(pathName, lastExecutionHandler.get(pathName.getAbsolutePath()))
|
||||
|| !isNewer(pathName, filterDate)) {
|
||||
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, Instant date) {
|
||||
if (date == null) return true;
|
||||
try {
|
||||
BasicFileAttributes attributes = Files.readAttributes(pathName.toPath(), BasicFileAttributes.class);
|
||||
return attributes.creationTime().toInstant().isAfter(date)
|
||||
|| attributes.lastModifiedTime().toInstant().isAfter(date);
|
||||
} catch (IOException e) {
|
||||
log.warn("File attributes could not be read", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.Properties;
|
||||
|
||||
@Slf4j
|
||||
public class LastExecutionHandler {
|
||||
private static final String FILE_NAME = "last-execution.properties";
|
||||
private final File file;
|
||||
private final Properties lastFileExecution;
|
||||
|
||||
public LastExecutionHandler(String path) {
|
||||
file = new File(path + "/" + FILE_NAME);
|
||||
lastFileExecution = loadLastFileExecution(file);
|
||||
}
|
||||
|
||||
public Properties loadLastFileExecution(File file) {
|
||||
Properties properties = new Properties();
|
||||
try (FileInputStream in = new FileInputStream(file)) {
|
||||
properties.load(in);
|
||||
} catch (IOException e) {
|
||||
log.info("Couldn't find or read {}", file.getPath(), e);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
public Instant get(String path) {
|
||||
if (!lastFileExecution.containsKey(path)) return null;
|
||||
return Instant.parse(lastFileExecution.getProperty(path));
|
||||
}
|
||||
|
||||
public void update(String path) {
|
||||
update(path, Instant.now());
|
||||
}
|
||||
|
||||
public void update(String path, Instant execution) {
|
||||
if (lastFileExecution == null) return;
|
||||
lastFileExecution.put(path, execution.toString());
|
||||
}
|
||||
|
||||
public void persist() {
|
||||
try (FileOutputStream out = new FileOutputStream(file)) {
|
||||
lastFileExecution.store(out, "MKVAudioSubtitleChanger - Last file execution");
|
||||
} catch (IOException e) {
|
||||
log.warn("Persisting last file execution dates failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackAttributes;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class SubtitleTrackComparator implements Comparator<TrackAttributes> {
|
||||
private final Set<String> preferredSubtitles;
|
||||
private final Set<String> hearingImpairedKeywords;
|
||||
|
||||
public SubtitleTrackComparator(Collection<String> preferredSubtitles, Collection<String> hearingImpairedKeywords) {
|
||||
this.preferredSubtitles = new HashSet<>(preferredSubtitles.stream().map(String::toLowerCase).toList());
|
||||
this.hearingImpairedKeywords = new HashSet<>(hearingImpairedKeywords.stream().map(String::toLowerCase).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public int compare(TrackAttributes track1, TrackAttributes track2) {
|
||||
int result = 0;
|
||||
|
||||
String track1Name = Strings.isNotBlank(track1.trackName()) ? track1.trackName().toLowerCase() : "";
|
||||
String track2Name = Strings.isNotBlank(track2.trackName()) ? track2.trackName().toLowerCase() : "";
|
||||
|
||||
if (preferredSubtitles.contains(track1Name)) result++;
|
||||
else for (String keyword: preferredSubtitles) if (track1Name.contains(keyword)) result++;
|
||||
|
||||
if (preferredSubtitles.contains(track2Name)) result--;
|
||||
else for (String keyword: preferredSubtitles) if (track2Name.contains(keyword)) result--;
|
||||
|
||||
|
||||
if (track1.hearingImpaired()) result--;
|
||||
else if (hearingImpairedKeywords.contains(track1Name)) result--;
|
||||
else for (String keyword: hearingImpairedKeywords) if (track1Name.contains(keyword)) result--;
|
||||
if (track2.hearingImpaired()) result++;
|
||||
else if (hearingImpairedKeywords.contains(track2Name)) result++;
|
||||
else for (String keyword: hearingImpairedKeywords) if (track2Name.contains(keyword)) 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.getAudioLang()))
|
||||
throw new CommandLine.TypeConversionException("Audio language invalid: " + attr.getAudioLang());
|
||||
if (!isLanguageValid(attr.getSubLang()))
|
||||
throw new CommandLine.TypeConversionException("Subtitle language invalid: " + attr.getSubLang());
|
||||
|
||||
return attr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.SubtitleTrackComparator;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.*;
|
||||
|
||||
import java.util.*;
|
||||
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(Arrays.stream(preferredSubtitles).toList(), hearingImpairedKeywords);
|
||||
this.commentaryKeywords = commentaryKeywords;
|
||||
this.hearingImpairedKeywords = hearingImpairedKeywords;
|
||||
this.forcedKeywords = forcedKeywords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for default matches and applies them if found.
|
||||
*/
|
||||
public void findAndApplyDefaultMatch(FileInfo fileInfo, AttributeConfig... configs) {
|
||||
Map<String, List<TrackAttributes>> audiosByLanguage = new HashMap<>(fileInfo.getTracks().size());
|
||||
Map<String, List<TrackAttributes>> subsByLanguage = new HashMap<>(fileInfo.getTracks().size());
|
||||
getPossibleDefaults(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.getAudioLang()) || audiosByLanguage.containsKey(config.getAudioLang()))
|
||||
&& ("OFF".equals(config.getSubLang()) || subsByLanguage.containsKey(config.getSubLang()))) {
|
||||
fileInfo.setMatchedConfig(config);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (fileInfo.getMatchedConfig() == null) return;
|
||||
|
||||
AttributeConfig match = fileInfo.getMatchedConfig();
|
||||
removeExistingDefaults(fileInfo);
|
||||
if (!"OFF".equals(match.getAudioLang())) applyNewDefault(fileInfo, audiosByLanguage.get(fileInfo.getMatchedConfig().getAudioLang()).get(0));
|
||||
if (!"OFF".equals(match.getSubLang())) applyNewDefault(fileInfo, subsByLanguage.get(fileInfo.getMatchedConfig().getSubLang()).stream().max(subtitleTrackComparator).get());
|
||||
}
|
||||
|
||||
/**
|
||||
* If match with xxx:OFF was found forced track in audio language is applied as default.
|
||||
* Forced track detection takes changes of {@link AttributeChangeProcessor#findAndApplyForcedTracks} into consideration.
|
||||
*/
|
||||
public void applyForcedAsDefault(FileInfo fileInfo) {
|
||||
AttributeConfig c = fileInfo.getMatchedConfig();
|
||||
if (c == null) return;
|
||||
if (!"OFF".equals(c.getAudioLang()) && "OFF".equals(c.getSubLang())) {
|
||||
getForcedTracks(fileInfo)
|
||||
.filter(track -> c.getAudioLang().equals(track.language()))
|
||||
.findFirst()
|
||||
.ifPresent(track -> applyNewDefault(fileInfo, track));
|
||||
}
|
||||
}
|
||||
|
||||
private Stream<TrackAttributes> getPossibleDefaults(List<TrackAttributes> tracks) {
|
||||
Stream<TrackAttributes> attributes = tracks.stream();
|
||||
|
||||
return attributes
|
||||
.filter(attr -> !attr.commentary())
|
||||
.filter(attr -> {
|
||||
if (attr.trackName() == null) return true;
|
||||
return commentaryKeywords.stream().noneMatch(keyword -> keyword.compareToIgnoreCase(attr.trackName()) == 0);
|
||||
})
|
||||
.filter(attr -> !attr.forced())
|
||||
.filter(attr -> {
|
||||
if (attr.trackName() == null) return true;
|
||||
return forcedKeywords.stream().noneMatch(keyword -> keyword.compareToIgnoreCase(attr.trackName()) == 0);
|
||||
});
|
||||
}
|
||||
|
||||
private void removeExistingDefaults(FileInfo fileInfo) {
|
||||
fileInfo.getTracks().stream()
|
||||
.filter(TrackAttributes::defaultt)
|
||||
.forEach(attr -> fileInfo.getChanges().getDefaultTrack().put(attr, false));
|
||||
}
|
||||
|
||||
private void applyNewDefault(FileInfo fileInfo, TrackAttributes targetDefault) {
|
||||
Map<TrackAttributes, Boolean> changes = fileInfo.getChanges().getDefaultTrack();
|
||||
if (changes.containsKey(targetDefault)) {
|
||||
changes.remove(targetDefault);
|
||||
} else {
|
||||
changes.put(targetDefault, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void findAndApplyForcedTracks(FileInfo fileInfo, boolean overwrite) {
|
||||
Stream<TrackAttributes> forcedTracks = getForcedTracks(fileInfo);
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
private Stream<TrackAttributes> getForcedTracks(FileInfo fileInfo) {
|
||||
return fileInfo.getTracks().stream()
|
||||
.filter(track -> {
|
||||
if (fileInfo.getChanges().getForcedTrack().containsKey(track)) return fileInfo.getChanges().getForcedTrack().get(track);
|
||||
return matchesForcedKeywords(track) || track.forced();
|
||||
});
|
||||
}
|
||||
|
||||
private boolean matchesForcedKeywords(TrackAttributes track) {
|
||||
return track.trackName() != null && forcedKeywords.stream().anyMatch(keyword -> track.trackName().toLowerCase().contains(keyword.toLowerCase(Locale.ROOT)));
|
||||
}
|
||||
|
||||
public void findAndApplyCommentaryTracks(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 findAndApplyHearingImpairedTracks(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,145 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.exceptions.MkvToolNixException;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.LastExecutionHandler;
|
||||
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.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Slf4j
|
||||
public abstract class AttributeUpdater {
|
||||
|
||||
protected final InputConfig config;
|
||||
protected final FileProcessor fileProcessor;
|
||||
protected final AttributeChangeProcessor attributeChangeProcessor;
|
||||
protected final LastExecutionHandler lastExecutionHandler;
|
||||
protected final ResultStatistic statistic = ResultStatistic.getInstance();
|
||||
|
||||
private final ExecutorService executor;
|
||||
|
||||
public AttributeUpdater(InputConfig config, FileProcessor fileProcessor, AttributeChangeProcessor attributeChangeProcessor, LastExecutionHandler lastExecutionHandler) {
|
||||
this.config = config;
|
||||
this.fileProcessor = fileProcessor;
|
||||
this.attributeChangeProcessor = attributeChangeProcessor;
|
||||
this.lastExecutionHandler = lastExecutionHandler;
|
||||
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);
|
||||
}
|
||||
|
||||
if (lastExecutionHandler != null) lastExecutionHandler.persist();
|
||||
|
||||
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 (lastExecutionHandler != null) lastExecutionHandler.update(fileInfo.getFile().getAbsolutePath());
|
||||
if (!fileInfo.getChanges().isEmpty()) {
|
||||
statistic.changePlanned();
|
||||
|
||||
if (config.isSafeMode()) {
|
||||
log.info("Planned changes [{}] for {}", changeLog(fileInfo), fileInfo.getFile().getPath());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
log.info("Committing changes [{}] to {}", changeLog(fileInfo), fileInfo.getFile().getPath());
|
||||
fileProcessor.update(fileInfo);
|
||||
statistic.changeSuccessful();
|
||||
} catch (IOException | MkvToolNixException e) {
|
||||
statistic.changeFailed();
|
||||
log.warn("Couldn't commit changes [{}] to {}", changeLog(fileInfo), fileInfo.getFile().getPath(), e);
|
||||
}
|
||||
} else if (fileInfo.getChanges().isEmpty()) {
|
||||
statistic.unchanged();
|
||||
} else {
|
||||
statistic.unknownFailed();
|
||||
}
|
||||
}
|
||||
|
||||
private String changeLog(FileInfo fileInfo) {
|
||||
List<String> changes = new ArrayList<>();
|
||||
if (fileInfo.getMatchedConfig() != null) changes.add("defaults " + fileInfo.getMatchedConfig().toStringShort());
|
||||
if (!fileInfo.getChanges().getForcedTrack().isEmpty()) changes.add("forced tags");
|
||||
if (!fileInfo.getChanges().getCommentaryTrack().isEmpty()) changes.add("commentary tags");
|
||||
if (!fileInfo.getChanges().getHearingImpairedTrack().isEmpty()) changes.add("hearing impaired tags");
|
||||
return String.join(", ", changes);
|
||||
}
|
||||
|
||||
// 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,109 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.LastExecutionHandler;
|
||||
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.Arrays;
|
||||
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, LastExecutionHandler lastExecutionHandler) {
|
||||
super(config, processor, attributeChangeProcessor, lastExecutionHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProgressBarBuilder pbBuilder() {
|
||||
return super.pbBuilder()
|
||||
.setUnit(" directories", 1);
|
||||
}
|
||||
|
||||
protected List<File> getFiles() {
|
||||
return Arrays.stream(config.getLibraryPath())
|
||||
.flatMap(path -> fileProcessor.loadDirectory(path.getPath(), config.getCoherent()).stream())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
log.info("Found coherent match {} for {}", matchedConfig.toStringShort(), rootDir.getPath());
|
||||
matchedFiles.forEach(fileInfo -> {
|
||||
attributeChangeProcessor.findAndApplyForcedTracks(fileInfo, this.config.isOverwriteForced());
|
||||
attributeChangeProcessor.applyForcedAsDefault(fileInfo);
|
||||
attributeChangeProcessor.findAndApplyCommentaryTracks(fileInfo);
|
||||
attributeChangeProcessor.findAndApplyHearingImpairedTracks(fileInfo);
|
||||
|
||||
checkStatusAndUpdate(fileInfo);
|
||||
});
|
||||
return; // match was found and process must be stopped
|
||||
}
|
||||
|
||||
// Couldn't match any config at current level. Resetting changes and trying one level deeper
|
||||
matchedFiles.forEach(fileInfo -> {
|
||||
fileInfo.resetChanges();
|
||||
fileInfo.setMatchedConfig(null);
|
||||
});
|
||||
|
||||
if (config.isForceCoherent()) {
|
||||
log.info("No coherent match found, skipping {}", rootDir.getPath());
|
||||
statistic.increaseUnchangedBy(files.size());
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("No coherent match found, attempting to find coherent match in child directories of {}", 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);
|
||||
statistic.unknownFailed();
|
||||
break;
|
||||
}
|
||||
|
||||
attributeChangeProcessor.findAndApplyDefaultMatch(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,176 @@
|
||||
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.commons.io.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");
|
||||
// mkvpropedit takes in the n-th track, based on the order of mkvmerge --idenfity
|
||||
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,51 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.LastExecutionHandler;
|
||||
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.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class SingleFileAttributeUpdater extends AttributeUpdater {
|
||||
|
||||
public SingleFileAttributeUpdater(InputConfig config, FileProcessor processor, AttributeChangeProcessor attributeChangeProcessor, LastExecutionHandler lastExecutionHandler) {
|
||||
super(config, processor, attributeChangeProcessor, lastExecutionHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ProgressBarBuilder pbBuilder() {
|
||||
return super.pbBuilder()
|
||||
.setUnit(" files", 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<File> getFiles() {
|
||||
return Arrays.stream(config.getLibraryPath())
|
||||
.flatMap(path -> fileProcessor.loadFiles(path.getPath()).stream())
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(File file) {
|
||||
FileInfo fileInfo = fileProcessor.readAttributes(file);
|
||||
|
||||
if (fileInfo.getTracks().isEmpty()) {
|
||||
log.warn("No attributes found for {}", file);
|
||||
statistic.unknownFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
attributeChangeProcessor.findAndApplyDefaultMatch(fileInfo, config.getAttributeConfig());
|
||||
attributeChangeProcessor.findAndApplyForcedTracks(fileInfo, config.isOverwriteForced());
|
||||
attributeChangeProcessor.applyForcedAsDefault(fileInfo);
|
||||
attributeChangeProcessor.findAndApplyCommentaryTracks(fileInfo);
|
||||
attributeChangeProcessor.findAndApplyHearingImpairedTracks(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,27 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation;
|
||||
|
||||
import jakarta.validation.ConstraintValidator;
|
||||
import jakarta.validation.ConstraintValidatorContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
|
||||
@Slf4j
|
||||
public class ValidFileValidator implements ConstraintValidator<ValidFile, File[]> {
|
||||
@Override
|
||||
public void initialize(ValidFile constraintAnnotation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(File[] files, ConstraintValidatorContext context) {
|
||||
if (files == null || files.length == 0) return false;
|
||||
for (File file: files) {
|
||||
if (!file.exists()) {
|
||||
log.error("{} does not exist", file.getPath());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.intimpl;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
public interface FileCollector {
|
||||
List<File> loadFiles(String path);
|
||||
|
||||
List<FileAttribute> loadAttributes(File file);
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.intimpl;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.MKVToolProperties;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.FileAttribute;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Log4j2
|
||||
public class MkvFileCollector implements FileCollector {
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
/**
|
||||
* @param path Is entered path, which leads to one file directly or a directory which will be loaded recursive
|
||||
* @return list of all files within the directory, if it's only a file, the file will be returned in a list
|
||||
*/
|
||||
@Override
|
||||
public List<File> loadFiles(String path) {
|
||||
File file = new File(path);
|
||||
if (file.isFile() && file.getAbsolutePath().endsWith(".mkv")) {
|
||||
return new ArrayList<File>() {{
|
||||
add(file);
|
||||
}};
|
||||
} else if (file.isDirectory()) {
|
||||
try (Stream<Path> paths = Files.walk(Paths.get(path))) {
|
||||
return paths
|
||||
.filter(Files::isRegularFile)
|
||||
.map(Path::toFile)
|
||||
.filter(f -> f.getAbsolutePath().endsWith(".mkv"))
|
||||
.collect(Collectors.toList());
|
||||
} catch (IOException e) {
|
||||
log.error("Couldn't find file or directory!", e);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param file Takes the file from which the attributes will be returned
|
||||
* @return list of all important attributes
|
||||
*/
|
||||
@Override
|
||||
public List<FileAttribute> loadAttributes(File file) {
|
||||
Map<String, Object> jsonMap;
|
||||
List<FileAttribute> fileAttributes = new ArrayList<>();
|
||||
try {
|
||||
String command = "";
|
||||
if (System.getProperty("os.name").toLowerCase().contains("windows")) {
|
||||
command = "\"" + MKVToolProperties.getInstance().getMkvmergePath() + "\"";
|
||||
} else {
|
||||
command = MKVToolProperties.getInstance().getMkvmergePath();
|
||||
}
|
||||
String[] array = new String[]{
|
||||
command,
|
||||
"--identify",
|
||||
"--identification-format",
|
||||
"json",
|
||||
file.getAbsoluteFile().toString()
|
||||
};
|
||||
|
||||
InputStream inputStream = Runtime.getRuntime().exec(array).getInputStream();
|
||||
jsonMap = mapper.readValue(inputStream, Map.class);
|
||||
List<Map<String, Object>> tracks = (List<Map<String, Object>>) jsonMap.get("tracks");
|
||||
if (tracks == null) {
|
||||
log.warn("Couldn't retrieve information of {}", file.getAbsoluteFile().toString());
|
||||
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),
|
||||
(String) attribute.get("type")));
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
log.error("File could not be found or loaded!");
|
||||
}
|
||||
return fileAttributes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class AttributeConfig {
|
||||
private final String audioLang;
|
||||
private final String subLang;
|
||||
|
||||
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(audioLang, that.audioLang) && Objects.equals(subLang, that.subLang);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(audioLang, subLang);
|
||||
}
|
||||
|
||||
public String toStringShort() {
|
||||
return audioLang + ":" + subLang;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AttributeConfig{"
|
||||
+ "audioLanguage='" + audioLang + '\''
|
||||
+ ", subtitleLanguage='" + subLang + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Log4j2
|
||||
@Getter
|
||||
public class FileAttribute {
|
||||
private int id;
|
||||
private String language;
|
||||
private String trackName;
|
||||
private boolean defaultTrack;
|
||||
private boolean forcedTrack;
|
||||
private String type;
|
||||
|
||||
public FileAttribute(int id, String language, String trackName, boolean defaultTrack, boolean forcedTrack, String type) {
|
||||
this.id = id;
|
||||
this.language = language;
|
||||
this.trackName = trackName;
|
||||
this.defaultTrack = defaultTrack;
|
||||
this.forcedTrack = forcedTrack;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static boolean pathIsValid(String path) {
|
||||
File file = new File(path);
|
||||
if(file.isFile()){
|
||||
return file.getAbsolutePath().endsWith(".mkv");
|
||||
}
|
||||
if(file.isDirectory()){
|
||||
try(Stream<Path> paths = Files.walk(Paths.get(path))){
|
||||
List<String> allPaths = paths
|
||||
.filter(Files::isRegularFile)
|
||||
.map(f -> f.toAbsolutePath().toString())
|
||||
.collect(Collectors.toList());
|
||||
for(String filePath : allPaths){
|
||||
if(! filePath.endsWith(".mkv")){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}catch(IOException e){
|
||||
log.error("Couldn't find file or directory!", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
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.time.Instant;
|
||||
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 = "paths to library", arity = "1..*")
|
||||
private File[] libraryPath;
|
||||
|
||||
@Option(names = {"-a", "--attribute-config"}, arity = "1..*", converter = AttributeConfigConverter.class,
|
||||
description = "List of audio:subtitle pairs for matching defaults in order (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", showDefaultValue = CommandLine.Help.Visibility.ALWAYS, description = "thread count")
|
||||
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-files"}, description = "ignores all files unchanged and previously processed")
|
||||
private boolean onlyNewFiles;
|
||||
@Option(names = {"-d", "--filter-date"}, defaultValue = Option.NULL_VALUE, description = "only consider files created newer than entered date (following ISO-8601 yyyy-MM-ddTHH:mm:ss.sssZ)")
|
||||
private Instant filterDate;
|
||||
@Option(names = {"-i", "--include-pattern"}, defaultValue = ".*", description = "include files matching pattern")
|
||||
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", showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
|
||||
split = ", ", description = "Keywords to identify forced tracks (Defaults will be overwritten)")
|
||||
private Set<String> forcedKeywords;
|
||||
@Option(names = {"--commentary-keywords"}, arity = "1..*", defaultValue = "comment, commentary, director", showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
|
||||
split = ", ", description = "Keywords to identify commentary tracks (Defaults will be overwritten)")
|
||||
private Set<String> commentaryKeywords;
|
||||
@Option(names = {"--hearing-impaired"}, arity = "1..*", defaultValue = "SDH, CC", showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
|
||||
split = ", ", description = "Keywords to identify hearing impaired tracks (Defaults will be overwritten")
|
||||
private Set<String> hearingImpaired;
|
||||
@Option(names = {"--preferred-subtitles"}, arity = "1..*", defaultValue = "unstyled", showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
|
||||
split = ", ", description = "Keywords to prefer specific subtitle tracks (Defaults will be overwritten)")
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
|
||||
@AllArgsConstructor
|
||||
public enum MkvToolNix {
|
||||
MKV_MERGE("mkvmerge"),
|
||||
MKV_PROP_EDIT("mkvpropedit");
|
||||
|
||||
private final String file;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Getter
|
||||
@Slf4j
|
||||
public class ResultStatistic {
|
||||
private static final String PRINT_TEMPLATE = "Total: %s, Changing: %s (Successful: %s, Failed %s), Unchanged: %s, Excluded: %s, Unknown/Failed: %s\nRuntime: %s";
|
||||
private static ResultStatistic instance;
|
||||
|
||||
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 long startTime = 0;
|
||||
private long runtime = 0;
|
||||
|
||||
public static ResultStatistic getInstance() {
|
||||
return getInstance(false);
|
||||
}
|
||||
|
||||
public static ResultStatistic getInstance(boolean reset) {
|
||||
if (instance == null || reset) {
|
||||
instance = new ResultStatistic();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public int total() {
|
||||
return changePlanned + unchanged + excluded + unknownFailed;
|
||||
}
|
||||
|
||||
public synchronized void changePlanned() {
|
||||
changePlanned++;
|
||||
}
|
||||
|
||||
public synchronized void changeSuccessful() {
|
||||
changeSuccessful++;
|
||||
}
|
||||
|
||||
public synchronized void changeFailed() {
|
||||
changeFailed++;
|
||||
}
|
||||
|
||||
public synchronized void unchanged() {
|
||||
unchanged++;
|
||||
}
|
||||
|
||||
public synchronized void increaseUnchangedBy(int amount) {
|
||||
unchanged += amount;
|
||||
}
|
||||
|
||||
public synchronized void excluded() {
|
||||
excluded++;
|
||||
}
|
||||
|
||||
public synchronized void unknownFailed() {
|
||||
unknownFailed++;
|
||||
}
|
||||
|
||||
public void startTimer() {
|
||||
startTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void stopTimer() {
|
||||
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
|
||||
public String toString() {
|
||||
return String.format(PRINT_TEMPLATE, total(), changePlanned, changeSuccessful, changeFailed, unchanged, excluded, unknownFailed, formatTimer());
|
||||
}
|
||||
}
|
||||
@@ -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 +
|
||||
']';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.model;
|
||||
|
||||
public enum TrackType {
|
||||
AUDIO,
|
||||
SUBTITLES;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.config.AttributeConfig;
|
||||
import at.pcgamingfreaks.yaml.YAML;
|
||||
import at.pcgamingfreaks.yaml.YamlInvalidContentException;
|
||||
import at.pcgamingfreaks.yaml.YamlKeyNotFoundException;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Log4j2
|
||||
public class ConfigUtil {
|
||||
public static List<AttributeConfig> loadConfig() {
|
||||
try(YAML yaml = new YAML(new File("config.yaml"))){
|
||||
return yaml.getKeysFiltered(".*audio.*").stream()
|
||||
.sorted()
|
||||
.map(elem -> elem.replace(".audio", ""))
|
||||
.map(elem -> createAttributeConfig(elem, yaml))
|
||||
.collect(Collectors.toList());
|
||||
}catch(YamlInvalidContentException | IOException e){
|
||||
log.fatal("Config could not be loaded");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static AttributeConfig createAttributeConfig(String key, YAML yaml) {
|
||||
try{
|
||||
return new AttributeConfig(
|
||||
yaml.getStringList(key + ".audio"),
|
||||
yaml.getStringList(key + ".subtitle"));
|
||||
}catch(YamlKeyNotFoundException e){
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class LanguageValidatorUtil {
|
||||
private static Set<String> ISO3_LANGUAGES;
|
||||
|
||||
static {
|
||||
try {
|
||||
ISO3_LANGUAGES = loadLanguageCodes();
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
private static Set<String> loadLanguageCodes() throws IOException {
|
||||
try (BufferedReader bf = new BufferedReader(new InputStreamReader(
|
||||
Objects.requireNonNull(LanguageValidatorUtil.class.getClassLoader().getResourceAsStream("language-codes"))))) {
|
||||
return bf.lines().collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isLanguageValid(String language) {
|
||||
return ISO3_LANGUAGES.contains(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();
|
||||
}
|
||||
42
src/main/resources/log4j2-debian.yml
Normal file
42
src/main/resources/log4j2-debian.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
Configuration:
|
||||
name: MainConfig
|
||||
|
||||
Properties:
|
||||
Property:
|
||||
- name: logDir
|
||||
value: ${sys:user.home}/.local/mkvaudiosubtitlechanger/logs
|
||||
- name: logPattern
|
||||
value: "%d{DEFAULT} | %-5level | %msg %n %throwable"
|
||||
|
||||
Appenders:
|
||||
RollingFile:
|
||||
name: FileAppender
|
||||
fileName: ${logDir}/application.log
|
||||
filePattern: ${logDir}/archive/application-%d{yyyy-MM-dd}-%i.log.gz
|
||||
PatternLayout:
|
||||
Pattern: logPattern
|
||||
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
|
||||
49
src/main/resources/log4j2-dev.yml
Normal file
49
src/main/resources/log4j2-dev.yml
Normal file
@@ -0,0 +1,49 @@
|
||||
Configuration:
|
||||
|
||||
Properties:
|
||||
Property:
|
||||
- name: logDir
|
||||
value: "./logs"
|
||||
- name: log_pattern
|
||||
value: "%d{DEFAULT} | %-5level | %thread | %C{1} | %msg %n %throwable"
|
||||
|
||||
Appenders:
|
||||
Console:
|
||||
name: Console_Out
|
||||
PatternLayout:
|
||||
Pattern: ${log_pattern}
|
||||
ThresholdFilter:
|
||||
level: debug
|
||||
RollingFile:
|
||||
name: FileAppender
|
||||
fileName: ${logDir}/application.log
|
||||
filePattern: ${logDir}/archive/application-%d{yyyy-MM-dd}-%i.log.gz
|
||||
PatternLayout:
|
||||
Pattern: logPattern
|
||||
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
|
||||
42
src/main/resources/log4j2-windows.yml
Normal file
42
src/main/resources/log4j2-windows.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
Configuration:
|
||||
name: WindowsConfig
|
||||
|
||||
Properties:
|
||||
Property:
|
||||
- name: logDir
|
||||
value: ${sys:user.home}/AppData/Local/MKVAudioSubtitleChanger/logs
|
||||
- name: logPattern
|
||||
value: "%d{DEFAULT} | %-5level | %msg %n %throwable"
|
||||
|
||||
Appenders:
|
||||
RollingFile:
|
||||
name: FileAppender
|
||||
fileName: ${logDir}/application.log
|
||||
filePattern: ${logDir}/archive/application-%d{yyyy-MM-dd}-%i.log.gz
|
||||
PatternLayout:
|
||||
Pattern: logPattern
|
||||
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,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: info
|
||||
Loggers:
|
||||
Root:
|
||||
level: debug
|
||||
AppenderRef:
|
||||
- ref: Console_Out
|
||||
- ref: FileAppender
|
||||
Logger:
|
||||
name: "com.zaxxer.hikari.HikariConfig"
|
||||
level: info
|
||||
AppenderRef:
|
||||
- ref: Console_Out
|
||||
- ref: FileAppender
|
||||
42
src/main/resources/log4j2.yml
Normal file
42
src/main/resources/log4j2.yml
Normal file
@@ -0,0 +1,42 @@
|
||||
Configuration:
|
||||
name: MainConfig
|
||||
|
||||
Properties:
|
||||
Property:
|
||||
- name: logDir
|
||||
value: ./logs
|
||||
- name: logPattern
|
||||
value: "%d{DEFAULT} | %-5level | %msg %n %throwable"
|
||||
|
||||
Appenders:
|
||||
RollingFile:
|
||||
name: FileAppender
|
||||
fileName: ${logDir}/application.log
|
||||
filePattern: ${logDir}/archive/application-%d{yyyy-MM-dd}-%i.log.gz
|
||||
PatternLayout:
|
||||
Pattern: logPattern
|
||||
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
|
||||
@@ -0,0 +1,23 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.config;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.CommandRunner;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.validation.ValidationExecutionStrategy;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.PathUtils.TEST_FILE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
public class PicoCliTest {
|
||||
|
||||
@Test
|
||||
void loadFilterDate() {
|
||||
CommandRunner underTest = new CommandRunner();
|
||||
new CommandLine(underTest)
|
||||
.setExecutionStrategy(new ValidationExecutionStrategy())
|
||||
.parseArgs("-d", "2012-12-12T12:12:12.00Z", TEST_FILE);
|
||||
assertEquals(Instant.parse("2012-12-12T12:12:12.00Z"), underTest.getConfig().getFilterDate());
|
||||
}
|
||||
}
|
||||
@@ -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,87 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.ResultStatistic;
|
||||
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.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.HashSet;
|
||||
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();
|
||||
when(attributes.creationTime()).thenReturn(FileTime.fromMillis(currentTime));
|
||||
when(attributes.lastModifiedTime()).thenReturn(FileTime.fromMillis(currentTime));
|
||||
FileFilter fileFilter = new FileFilter(excludedDirs, Pattern.compile(pattern), Instant.ofEpochMilli(currentTime).plus(filterDateOffset, ChronoUnit.SECONDS), null);
|
||||
|
||||
try (MockedStatic<Files> mockedFiles = Mockito.mockStatic(Files.class)) {
|
||||
mockedFiles
|
||||
.when(() -> Files.readAttributes(any(), eq(BasicFileAttributes.class)))
|
||||
.thenReturn(attributes);
|
||||
|
||||
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,74 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class LastExecutionHandlerTest {
|
||||
private static final String LAST_EXECUTION_PATH = ".";
|
||||
private static final String LAST_EXECUTION_FILE = "./last-execution.properties";
|
||||
private static final String TEST_MKV_FILE = "/arst/file.mkv";
|
||||
|
||||
@AfterEach
|
||||
void destruct() {
|
||||
File file = new File(LAST_EXECUTION_FILE);
|
||||
if (file.exists()) file.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingFile() throws IOException {
|
||||
LastExecutionHandler underTest = new LastExecutionHandler(LAST_EXECUTION_PATH);
|
||||
assertNull(underTest.get(TEST_MKV_FILE));
|
||||
underTest.update(TEST_MKV_FILE);
|
||||
assertNotNull(underTest.get(TEST_MKV_FILE));
|
||||
underTest.persist();
|
||||
File file = new File(LAST_EXECUTION_FILE);
|
||||
assertTrue(file.exists());
|
||||
assertTrue(Files.readString(file.toPath()).contains(TEST_MKV_FILE + "="));
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyFile() throws IOException {
|
||||
File file = new File(LAST_EXECUTION_FILE);
|
||||
file.createNewFile();
|
||||
missingFile(); // does the checks needed for empty file case
|
||||
}
|
||||
|
||||
@Test
|
||||
void existingFileNoChanges() throws IOException {
|
||||
File file = new File(LAST_EXECUTION_FILE);
|
||||
file.createNewFile();
|
||||
Files.writeString(file.toPath(), TEST_MKV_FILE + "=" + Instant.now());
|
||||
String expected = Files.readString(file.toPath()).replace(":", "\\:");
|
||||
|
||||
LastExecutionHandler underTest = new LastExecutionHandler(LAST_EXECUTION_PATH);
|
||||
assertNotNull(underTest.get(TEST_MKV_FILE));
|
||||
underTest.persist();
|
||||
File file1 = new File(LAST_EXECUTION_FILE);
|
||||
assertTrue(file1.exists());
|
||||
assertTrue(Files.readString(file.toPath()).contains(expected), "File contains expected value");
|
||||
}
|
||||
|
||||
@Test
|
||||
void existingFileWithChanges() throws IOException {
|
||||
File file = new File(LAST_EXECUTION_FILE);
|
||||
file.createNewFile();
|
||||
Files.writeString(file.toPath(), TEST_MKV_FILE + "=" + Instant.now());
|
||||
String expected = Files.readString(file.toPath());
|
||||
|
||||
LastExecutionHandler underTest = new LastExecutionHandler(LAST_EXECUTION_PATH);
|
||||
assertNotNull(underTest.get(TEST_MKV_FILE));
|
||||
underTest.update(TEST_MKV_FILE);
|
||||
assertNotNull(underTest.get(TEST_MKV_FILE));
|
||||
underTest.persist();
|
||||
File file1 = new File(LAST_EXECUTION_FILE);
|
||||
assertTrue(file1.exists());
|
||||
assertNotEquals(expected, Files.readString(file.toPath()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
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 Stream<Arguments> compareArguments() {
|
||||
return Stream.of(
|
||||
Arguments.of(attr(""), attr(""), 0),
|
||||
Arguments.of(attr("pref"), attr(""), 1),
|
||||
Arguments.of(attr(""), attr("pref"), -1),
|
||||
Arguments.of(attr("pref"), attr("pref"), 0),
|
||||
|
||||
Arguments.of(attr("", true), attr("", true), 0),
|
||||
Arguments.of(attr("", true), attr(""), -1),
|
||||
Arguments.of(attr("CC", true), attr(""), -1),
|
||||
Arguments.of(attr("CC"), attr(""), -1),
|
||||
Arguments.of(attr(""), attr("", true), 1),
|
||||
Arguments.of(attr(""), attr("CC", true), 1),
|
||||
Arguments.of(attr(""), attr("CC"), 1),
|
||||
|
||||
Arguments.of(attr("pref", true), attr("pref"), -1),
|
||||
Arguments.of(attr("pref", true), attr("pref", true), 0),
|
||||
Arguments.of(attr("pref"), attr("pref", true), 1),
|
||||
Arguments.of(attr("", true), attr("pref"), -2),
|
||||
Arguments.of(attr("pref"), attr("", true), 2),
|
||||
|
||||
Arguments.of(attr(null), attr(null), 0),
|
||||
Arguments.of(attr(null), attr(""), 0),
|
||||
Arguments.of(attr(null), attr("pref"), -1),
|
||||
Arguments.of(attr(""), attr(null), 0),
|
||||
Arguments.of(attr("pref"), attr(null), 1)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("compareArguments")
|
||||
void compare(TrackAttributes track1, TrackAttributes track2, int expected) {
|
||||
SubtitleTrackComparator comparator = new SubtitleTrackComparator(List.of("pref"), List.of("CC", "SDH"));
|
||||
int actual = comparator.compare(track1, track2);
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
private static TrackAttributes attr(String trackname) {
|
||||
return attr(trackname, false);
|
||||
}
|
||||
|
||||
private static TrackAttributes attr(String trackName, boolean hearingImpaired) {
|
||||
return new TrackAttributes(0, "", trackName, false, false, false, hearingImpaired, TrackType.SUBTITLES);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,349 @@
|
||||
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.TrackAttributeUtil.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class AttributeChangeProcessorTest {
|
||||
|
||||
private static Stream<Arguments> findAndApplyDefaultMatch() {
|
||||
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_GER, withName(SUB_GER, "SDH")),
|
||||
arr(a("ger:ger")), "ger:ger",
|
||||
Map.ofEntries(on(AUDIO_GER), on(withName(SUB_GER, "SDH")))
|
||||
),
|
||||
Arguments.of(
|
||||
List.of(AUDIO_GER, withName(SUB_GER, "SDH"), SUB_GER),
|
||||
arr(a("ger:ger")), "ger:ger",
|
||||
Map.ofEntries(on(AUDIO_GER), on(SUB_GER))
|
||||
),
|
||||
Arguments.of(
|
||||
List.of(AUDIO_GER, SUB_GER_HEARING),
|
||||
arr(a("ger:ger")), "ger:ger",
|
||||
Map.ofEntries(on(AUDIO_GER), on(SUB_GER_HEARING))
|
||||
),
|
||||
Arguments.of(
|
||||
List.of(AUDIO_GER, SUB_GER_HEARING, SUB_GER),
|
||||
arr(a("ger:ger")), "ger:ger",
|
||||
Map.ofEntries(on(AUDIO_GER), on(SUB_GER))
|
||||
),
|
||||
Arguments.of(
|
||||
List.of(AUDIO_GER, SUB_ENG_HEARING, SUB_GER),
|
||||
arr(a("ger:eng")), "ger:eng",
|
||||
Map.ofEntries(on(AUDIO_GER), on(SUB_ENG_HEARING))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("findAndApplyDefaultMatch")
|
||||
void findAndApplyDefaultMatch(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.findAndApplyDefaultMatch(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 Stream<Arguments> applyForcedAsDefault() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
List.of(AUDIO_GER, SUB_GER_FORCED),
|
||||
a("ger:OFF"),
|
||||
Map.ofEntries(on(SUB_GER_FORCED))
|
||||
),
|
||||
Arguments.of(
|
||||
List.of(AUDIO_GER, SUB_GER_FORCED, SUB_GER),
|
||||
a("ger:OFF"),
|
||||
Map.ofEntries(on(SUB_GER_FORCED))
|
||||
),
|
||||
Arguments.of(
|
||||
List.of(AUDIO_GER, withName(SUB_GER, "forced")),
|
||||
a("ger:OFF"),
|
||||
Map.ofEntries(on(withName(SUB_GER, "forced")))
|
||||
),
|
||||
Arguments.of(
|
||||
List.of(AUDIO_GER, SUB_GER_FORCED, SUB_ENG),
|
||||
a("ger:eng"),
|
||||
Map.ofEntries()
|
||||
),
|
||||
Arguments.of(
|
||||
List.of(AUDIO_GER, SUB_GER_FORCED, SUB_ENG),
|
||||
null,
|
||||
Map.ofEntries()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("applyForcedAsDefault")
|
||||
void applyForcedAsDefault(List<TrackAttributes> tracks, AttributeConfig config, Map<TrackAttributes, Boolean> changes) {
|
||||
AttributeChangeProcessor attributeChangeProcessor = new AttributeChangeProcessor(new String[]{}, Set.of("forced"), Set.of(""), Set.of(""));
|
||||
FileInfo fileInfo = new FileInfo(null);
|
||||
fileInfo.addTracks(tracks);
|
||||
fileInfo.setMatchedConfig(config);
|
||||
|
||||
attributeChangeProcessor.applyForcedAsDefault(fileInfo);
|
||||
|
||||
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 Stream<Arguments> getPossibleDefaults() {
|
||||
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, withName(AUDIO_GER, "SDH"))),
|
||||
Arguments.of(List.of(AUDIO_GER, AUDIO_ENG, withName(AUDIO_GER, "sdh"), SUB_GER), Set.of(AUDIO_GER, AUDIO_ENG, SUB_GER, withName(AUDIO_GER, "sdh"))),
|
||||
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, AUDIO_GER_HEARING))
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("getPossibleDefaults")
|
||||
void getPossibleDefaults(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("getPossibleDefaults"))
|
||||
.findFirst();
|
||||
|
||||
assertTrue(method.isPresent());
|
||||
Method underTest = method.get();
|
||||
underTest.setAccessible(true);
|
||||
List<TrackAttributes> result = ((Stream<TrackAttributes>) underTest.invoke(attributeChangeProcessor, tracks)).toList();
|
||||
assertEquals(expected.size(), result.size());
|
||||
for (TrackAttributes track : result) {
|
||||
assertTrue(expected.contains(track));
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Arguments> getForcedTracks() {
|
||||
return Stream.of(
|
||||
Arguments.of(List.of(withName(SUB_GER, "forced")), Map.of(), Set.of(withName(SUB_GER, "forced"))),
|
||||
Arguments.of(List.of(SUB_GER_FORCED), Map.of(), Set.of(SUB_GER_FORCED)),
|
||||
Arguments.of(List.of(SUB_GER_FORCED, withName(SUB_GER, "forced")), Map.of(), Set.of(SUB_GER_FORCED, withName(SUB_GER, "forced"))),
|
||||
Arguments.of(List.of(SUB_GER_FORCED, withName(SUB_GER, "forced")), Map.of(SUB_GER_FORCED, false), Set.of(withName(SUB_GER, "forced"))),
|
||||
Arguments.of(List.of(SUB_GER, withName(SUB_GER, "forced")), Map.of(SUB_GER, true), Set.of(SUB_GER, withName(SUB_GER, "forced")))
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("getForcedTracks")
|
||||
void getForcedTracks(List<TrackAttributes> tracks, Map<TrackAttributes, Boolean> changes, Set<TrackAttributes> expected) throws InvocationTargetException, IllegalAccessException {
|
||||
AttributeChangeProcessor attributeChangeProcessor = new AttributeChangeProcessor(new String[]{}, Set.of("forced"), Set.of(), Set.of());
|
||||
FileInfo fileInfo = new FileInfo(null);
|
||||
fileInfo.addTracks(tracks);
|
||||
changes.forEach((key, val) -> fileInfo.getChanges().getForcedTrack().put(key, val));
|
||||
Optional<Method> method = Arrays.stream(AttributeChangeProcessor.class.getDeclaredMethods())
|
||||
.filter(m -> m.getName().equals("getForcedTracks"))
|
||||
.findFirst();
|
||||
|
||||
assertTrue(method.isPresent());
|
||||
Method underTest = method.get();
|
||||
underTest.setAccessible(true);
|
||||
List<TrackAttributes> result = ((Stream<TrackAttributes>) underTest.invoke(attributeChangeProcessor, fileInfo)).toList();
|
||||
assertEquals(expected.size(), result.size());
|
||||
for (TrackAttributes track : result) {
|
||||
assertTrue(expected.contains(track));
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Arguments> findAndApplyForcedTracks() {
|
||||
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("findAndApplyForcedTracks")
|
||||
void findAndApplyForcedTracks(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.findAndApplyForcedTracks(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> findAndApplyCommentaryTracks() {
|
||||
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("findAndApplyCommentaryTracks")
|
||||
void findAndApplyCommentaryTracks(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.findAndApplyCommentaryTracks(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> findAndApplyHearingImpairedTracks() {
|
||||
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("findAndApplyHearingImpairedTracks")
|
||||
void findAndApplyHearingImpairedTracks(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.findAndApplyHearingImpairedTracks(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,66 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.LastExecutionHandler;
|
||||
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.TrackAttributeUtil.AUDIO_GER;
|
||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.PathUtils.TEST_FILE;
|
||||
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, new LastExecutionHandler("")) {
|
||||
@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(new File(TEST_FILE));
|
||||
fileInfo.setMatchedConfig(config);
|
||||
if(attr != null) fileInfo.getChanges().getDefaultTrack().put(attr, true);
|
||||
return fileInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.CommandRunner;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.LastExecutionHandler;
|
||||
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.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.PathUtils.TEST_DIR;
|
||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.TrackAttributeUtil.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CoherentAttributeUpdaterTest {
|
||||
@Mock(lenient = true)
|
||||
FileProcessor fileProcessor;
|
||||
|
||||
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());
|
||||
LastExecutionHandler lastExecutionHandler = new LastExecutionHandler("");
|
||||
CoherentAttributeUpdater updater = new CoherentAttributeUpdater(config, fileProcessor, attributeChangeProcessor, lastExecutionHandler);
|
||||
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);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> process() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
arr(a("ger:ger")), a("ger:ger"),
|
||||
List.of(
|
||||
List.of(AUDIO_GER, SUB_GER),
|
||||
List.of(AUDIO_GER, SUB_GER)
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(on(AUDIO_GER), on(SUB_GER)),
|
||||
Map.ofEntries(on(AUDIO_GER), on(SUB_GER))
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(),
|
||||
Map.ofEntries()
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(),
|
||||
Map.ofEntries()
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(),
|
||||
Map.ofEntries()
|
||||
)
|
||||
),
|
||||
Arguments.of(
|
||||
arr(a("eng:eng"), a("ger:ger")), a("ger:ger"),
|
||||
List.of(
|
||||
List.of(SUB_ENG, AUDIO_GER, SUB_GER),
|
||||
List.of(AUDIO_ENG, SUB_ENG, AUDIO_GER, SUB_GER)
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(on(AUDIO_GER), on(SUB_GER)),
|
||||
Map.ofEntries(on(AUDIO_GER), on(SUB_GER))
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(),
|
||||
Map.ofEntries()
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(),
|
||||
Map.ofEntries()
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(),
|
||||
Map.ofEntries()
|
||||
)
|
||||
),
|
||||
Arguments.of(
|
||||
arr(a("eng:eng"), a("ger:ger")), a("eng:eng"),
|
||||
List.of(
|
||||
List.of(AUDIO_ENG, withName(SUB_ENG, "SDH"), AUDIO_GER, SUB_GER),
|
||||
List.of(AUDIO_ENG, SUB_ENG, AUDIO_GER, SUB_GER)
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(on(AUDIO_ENG), on(withName(SUB_ENG, "SDH"))),
|
||||
Map.ofEntries(on(AUDIO_ENG), on(SUB_ENG))
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(),
|
||||
Map.ofEntries()
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(),
|
||||
Map.ofEntries()
|
||||
),
|
||||
List.of(
|
||||
Map.ofEntries(on(withName(SUB_ENG, "SDH"))),
|
||||
Map.ofEntries()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("process")
|
||||
void process(AttributeConfig[] attributeConfigs, AttributeConfig expectedMatch,
|
||||
List<List<TrackAttributes>> tracks,
|
||||
List<Map<TrackAttributes, Boolean>> defaultExp,
|
||||
List<Map<TrackAttributes, Boolean>> forcedExp,
|
||||
List<Map<TrackAttributes, Boolean>> commentaryExp,
|
||||
List<Map<TrackAttributes, Boolean>> hearingImpairedExp) {
|
||||
InputConfig config = new InputConfig();
|
||||
config.setThreads(1);
|
||||
config.setSafeMode(true);
|
||||
config.setAttributeConfig(attributeConfigs);
|
||||
FileProcessor fileProcessor = spy(FileProcessor.class);
|
||||
|
||||
List<File> testMkvFiles = new ArrayList<>();
|
||||
List<FileInfo> testFileInfo = new ArrayList<>();
|
||||
for (int i = 0; i < tracks.size(); i++) {
|
||||
List<TrackAttributes> tracks1 = tracks.get(i);
|
||||
File file = new File(TEST_DIR + i);
|
||||
FileInfo fileInfo = new FileInfo(file);
|
||||
fileInfo.addTracks(tracks1);
|
||||
doReturn(fileInfo).when(fileProcessor).readAttributes(file);
|
||||
|
||||
testMkvFiles.add(file);
|
||||
testFileInfo.add(fileInfo);
|
||||
}
|
||||
doReturn(testMkvFiles).when(fileProcessor).loadFiles(any());
|
||||
|
||||
AttributeChangeProcessor attributeChangeProcessor = new AttributeChangeProcessor(new String[]{"pref"}, Set.of("forced"), Set.of("commentary"), Set.of("SDH"));
|
||||
LastExecutionHandler lastExecutionHandler = new LastExecutionHandler("");
|
||||
CoherentAttributeUpdater underTest = new CoherentAttributeUpdater(config, fileProcessor, attributeChangeProcessor, lastExecutionHandler);
|
||||
|
||||
underTest.process(new File(""));
|
||||
|
||||
for (int i = 0; i < testFileInfo.size(); i++) {
|
||||
FileInfo fileInfo = testFileInfo.get(i);
|
||||
assertEquals(expectedMatch, fileInfo.getMatchedConfig());
|
||||
assertEquals(fileInfo.getChanges().getDefaultTrack().size(), defaultExp.get(i).size());
|
||||
defaultExp.get(i).forEach((key, val) -> assertEquals(val, fileInfo.getChanges().getDefaultTrack().get(key), "Default track flag"));
|
||||
|
||||
assertEquals(fileInfo.getChanges().getForcedTrack().size(), forcedExp.get(i).size());
|
||||
forcedExp.get(i).forEach((key, val) -> assertEquals(val, fileInfo.getChanges().getForcedTrack().get(key), "Forced track flag"));
|
||||
|
||||
assertEquals(fileInfo.getChanges().getCommentaryTrack().size(), commentaryExp.get(i).size());
|
||||
commentaryExp.get(i).forEach((key, val) -> assertEquals(val, fileInfo.getChanges().getCommentaryTrack().get(key), "Commentary track flag"));
|
||||
|
||||
assertEquals(fileInfo.getChanges().getHearingImpairedTrack().size(), hearingImpairedExp.get(i).size());
|
||||
hearingImpairedExp.get(i).forEach((key, val) -> assertEquals(val, fileInfo.getChanges().getHearingImpairedTrack().get(key), "Hearing Impaired track flag"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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,98 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.processors;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.impl.LastExecutionHandler;
|
||||
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.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.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.PathUtils.TEST_DIR;
|
||||
import static at.pcgamingfreaks.mkvaudiosubtitlechanger.util.TrackAttributeUtil.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class SingleFileAttributeUpdaterTest {
|
||||
|
||||
private static Stream<Arguments> process() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
arr(a("ger:OFF")), a("ger:OFF"),
|
||||
List.of(AUDIO_GER, SUB_GER_FORCED),
|
||||
Map.ofEntries(on(AUDIO_GER), on(SUB_GER_FORCED)),
|
||||
Map.ofEntries(),
|
||||
Map.ofEntries(),
|
||||
Map.ofEntries()
|
||||
),
|
||||
Arguments.of(
|
||||
arr(a("ger:ger")), a("ger:ger"),
|
||||
List.of(AUDIO_GER, SUB_GER, withName(AUDIO_GER, "SDH"), withName(AUDIO_GER, "commentary"), withName(SUB_GER, "Forced")),
|
||||
Map.ofEntries(on(AUDIO_GER), on(SUB_GER)),
|
||||
Map.ofEntries(on(withName(SUB_GER, "Forced"))),
|
||||
Map.ofEntries(on(withName(AUDIO_GER, "commentary"))),
|
||||
Map.ofEntries(on(withName(AUDIO_GER, "SDH")))
|
||||
),
|
||||
Arguments.of(
|
||||
arr(a("ger:OFF")), a("ger:OFF"),
|
||||
List.of(AUDIO_GER, SUB_GER, withName(AUDIO_GER, "SDH"), withName(AUDIO_GER, "commentary"), withName(SUB_GER, "Forced")),
|
||||
Map.ofEntries(on(AUDIO_GER), on(withName(SUB_GER, "Forced"))),
|
||||
Map.ofEntries(on(withName(SUB_GER, "Forced"))),
|
||||
Map.ofEntries(on(withName(AUDIO_GER, "commentary"))),
|
||||
Map.ofEntries(on(withName(AUDIO_GER, "SDH")))
|
||||
),
|
||||
Arguments.of(
|
||||
arr(a("ger:eng")), null,
|
||||
List.of(AUDIO_GER, SUB_GER, withName(AUDIO_GER, "SDH"), withName(AUDIO_GER, "commentary"), withName(SUB_GER, "Forced")),
|
||||
Map.ofEntries(),
|
||||
Map.ofEntries(on(withName(SUB_GER, "Forced"))),
|
||||
Map.ofEntries(on(withName(AUDIO_GER, "commentary"))),
|
||||
Map.ofEntries(on(withName(AUDIO_GER, "SDH")))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("process")
|
||||
void process(AttributeConfig[] attributeConfigs, AttributeConfig expectedMatch,
|
||||
List<TrackAttributes> tracks,
|
||||
Map<TrackAttributes, Boolean> defaultExp,
|
||||
Map<TrackAttributes, Boolean> forcedExp,
|
||||
Map<TrackAttributes, Boolean> commentaryExp,
|
||||
Map<TrackAttributes, Boolean> hearingImpairedExp) {
|
||||
InputConfig config = new InputConfig();
|
||||
config.setThreads(1);
|
||||
config.setSafeMode(true);
|
||||
config.setAttributeConfig(attributeConfigs);
|
||||
FileInfo fileInfo = new FileInfo(new File(TEST_DIR));
|
||||
fileInfo.addTracks(tracks);
|
||||
FileProcessor fileProcessor = spy(FileProcessor.class);
|
||||
doReturn(fileInfo).when(fileProcessor).readAttributes(any());
|
||||
AttributeChangeProcessor attributeChangeProcessor = new AttributeChangeProcessor(new String[]{"pref"}, Set.of("forced"), Set.of("commentary"), Set.of("SDH"));
|
||||
LastExecutionHandler lastExecutionHandler = new LastExecutionHandler("");
|
||||
SingleFileAttributeUpdater underTest = new SingleFileAttributeUpdater(config, fileProcessor, attributeChangeProcessor, lastExecutionHandler);
|
||||
|
||||
underTest.process(fileInfo.getFile());
|
||||
|
||||
assertEquals(expectedMatch, fileInfo.getMatchedConfig());
|
||||
assertEquals(fileInfo.getChanges().getDefaultTrack().size(), defaultExp.size());
|
||||
defaultExp.forEach((key, val) -> assertEquals(val, fileInfo.getChanges().getDefaultTrack().get(key), "Default track flag"));
|
||||
|
||||
assertEquals(fileInfo.getChanges().getForcedTrack().size(), forcedExp.size());
|
||||
forcedExp.forEach((key, val) -> assertEquals(val, fileInfo.getChanges().getForcedTrack().get(key), "Forced track flag"));
|
||||
|
||||
assertEquals(fileInfo.getChanges().getCommentaryTrack().size(), commentaryExp.size());
|
||||
commentaryExp.forEach((key, val) -> assertEquals(val, fileInfo.getChanges().getCommentaryTrack().get(key), "Commentary track flag"));
|
||||
|
||||
assertEquals(fileInfo.getChanges().getHearingImpairedTrack().size(), hearingImpairedExp.size());
|
||||
hearingImpairedExp.forEach((key, val) -> assertEquals(val, fileInfo.getChanges().getHearingImpairedTrack().get(key), "Hearing Impaired track flag"));
|
||||
}
|
||||
}
|
||||
@@ -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 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()[0].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[]{TEST_DIR, "/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,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,0 +1,51 @@
|
||||
package at.pcgamingfreaks.mkvaudiosubtitlechanger.util;
|
||||
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.AttributeConfig;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackAttributes;
|
||||
import at.pcgamingfreaks.mkvaudiosubtitlechanger.model.TrackType;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class TrackAttributeUtil {
|
||||
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, "eng", "", 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 final TrackAttributes SUB_GER_HEARING = new TrackAttributes(0, "ger", "", false, false, false, true, TrackType.SUBTITLES);
|
||||
public static final TrackAttributes SUB_ENG_HEARING = new TrackAttributes(1, "eng", "", false, false, false, true, 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());
|
||||
}
|
||||
|
||||
public static AttributeConfig[] arr(AttributeConfig... configs) {
|
||||
return configs;
|
||||
}
|
||||
|
||||
public static AttributeConfig a(String config) {
|
||||
String[] split = config.split(":");
|
||||
return new AttributeConfig(split[0], split[1]);
|
||||
}
|
||||
|
||||
public static Map.Entry<TrackAttributes, Boolean> on(TrackAttributes track) {
|
||||
return Map.entry(track, true);
|
||||
}
|
||||
|
||||
public static Map.Entry<TrackAttributes, Boolean> off(TrackAttributes track) {
|
||||
return Map.entry(track, false);
|
||||
}
|
||||
|
||||
}
|
||||
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>
|
||||
Reference in New Issue
Block a user