SubKt
SubKt is a highly configurable toolkit for fansubbing automation written in Kotlin for Gradle. Documentation can be found at https://github.com/Myaamori/SubKt/blob/master/docs/subkt/index.md
Install / Use
/learn @Myaamori/SubKtREADME
Table of Contents
- Table of Contents
- What is SubKt?
- Quickstart Guide
- Manual
- Preliminaries
- Batch Tasks
- Flexible Configuration With External Properties
- Template Syntax
- A Note on Task Properties
- Per-Task Variables (Or: Avoiding Having to Declare the Same Directory Structure Five Times Over)
- Kotlin Primer
- Example: Publishing to WordPress
- Example: Moving Repeat Task Configurations to an External Function
- Advanced Usage: Developing in IntelliJ IDEA
- Advanced Usage: Custom Tasks
- Advanced Usage: Using Pure Gradle
What is SubKt?
SubKt is a highly configurable framework for fansubbing that enables automation of a wide array of fansubbing-related tasks. Features include:
- Merging multiple ASS files
- Muxing files into a Matroska (MKV) container
- Easy batching
- Preprocessing of ASS files such as shifting song files, automatically incrementing the layer of dialogue lines, removing comments, and more
- Generating chapters from an ASS file using Significance-style syntax
- Automatic font validation
- Support for Merge Scripts template files
- Support for Autoswapper syntax for generating e.g. secondary honorifics tracks
- Running Aegisub automations using Aegisub CLI
- Generating torrent files
- Uploading files via (S)FTP
- Publishing to Nyaa/Anidex
- Discord notifications
- A clean Kotlin-based domain-specific language (DSL) which makes it easy to combine files and outputs of different tasks
- A powerful property system for easy configuration and reusability
- ??? (feature requests welcome -- open an issue and describe the feature you need and your use case!)
SubKt makes no assumptions on the structure of your project or what steps are required to release it, while also making it as simple as possible to configure the toolchain according to your needs.
SubKt is based on Gradle, and by simply declaring the output of one task as the input of another, Gradle will automatically figure out which tasks depend on which other tasks, meaning there is often no need to manually specify the order of the tasks.
SubKt makes it possible to do everything from muxing to release in one go, with one single command. Take the following complete example, where we merge ASS files, generate chapters, generate a second honorifics track, upload files to a seedbox, and publish the torrent, all by issuing one command.
$ ./gradlew startSeeding.02
> Task :chapters.02
> Task :merge.02
> Task :swap.02
> Task :mux.02
Output: Eizouken ni wa Te wo Dasu na! - 02 [D536A60F].mkv
> Task :ftp.02
> Task :torrent.02
Eizouken ni wa Te wo Dasu na! - 02 [D536A60F].mkv [02/Eizouken ni wa Te wo Dasu na! - 02 [D536A60F].mkv]
> Task :nyaa.02
Uploaded torrent: https://nyaa.si/view/[...] [Eizouken ni wa Te wo Dasu na! - 02 [D536A60F].mkv]
> Task :startSeeding.02
This was achieved using the below configuration:
subs {
readProperties("sub.properties")
episodes("01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12")
merge {
from("$episode/eizouken_$episode.ass") {
incrementLayer(10)
}
from(glob("$episode/eizouken$episode-ts*.ass"))
from("songs/eizouken_OP.ass") {
syncTargetTime(getAs<Duration>("opshift"))
}
from("songs/eizouken_ED.ass") {
syncTargetTime(getAs<Duration>("edshift"))
}
}
chapters {
// chapters defined using Significance syntax
from("$episode/eizouken_${episode}.ass")
}
swap {
from(merge.item())
}
mux {
title("Eizouken ni wa Te wo Dasu na! - $episode")
from("$episode/eizouken${episode}_premux.mkv") {
tracks {
include(track.type == TrackType.AUDIO || track.type == TrackType.VIDEO)
lang("jpn")
}
attachments {
include(false)
}
}
from(merge.item()) {
tracks {
name("English")
lang("eng")
default(true)
}
}
from(swap.item()) {
tracks {
name("English (Honorifics)")
lang("enm")
}
}
chapters(chapters.item()) {
lang("eng")
charset("UTF-8")
}
attach("$episode/fonts") {
includeExtensions("ttf", "otf")
}
// we don't know the CRC until after the file has been muxed,
// so pass filename as a lambda (surrounded by braces)
// which won't be evaluated until it's needed
out { "$episode/Eizouken ni wa Te wo Dasu na! - $episode [$crc].mkv" }
}
torrent {
trackers(getList("trackers"))
from(mux.item())
out("$episode/Eizouken ni wa Te wo Dasu na! - $episode.torrent")
}
nyaa {
from(torrent.item())
username(get("torrentuser"))
password(get("torrentpass"))
category(ANIME_ENGLISH)
torrentDescription(getFile("mytemplate.vm"))
}
// shared configuration for both FTP tasks
fun FTP.configure() {
host(get("ftphost"))
port(getAs<Int>("ftpport"))
username(get("ftpuser"))
password(get("ftppass"))
}
ftp {
from(mux.item())
into("/downloads")
configure()
}
"startSeeding"<FTP> {
// wait until files have been uploaded and torrent
// has been published to start seeding
dependsOn(ftp.item(), nyaa.item())
from(torrent.item())
into("/torrents")
configure()
}
}
In addition, an external sub.properties file, included at the top, contains the following:
02.opshift=0:04:06.55
02.edshift=0:23:39.48
trackers=http://nyaa.tracker.wf:7777/announce
torrentuser=nyaauser
torrentpass=nyaapass
ftphost=ftp.example.com
ftpport=980
ftpuser=username
ftppass=password
Quickstart Guide
To use SubKt you will need:
Ensure that both java and mkvmerge are available on the PATH.
Instructions for setting the PATH for JDK 14 on Windows can be found here.
The same procedure can be used to add mkvmerge to the PATH; take note of where you installed/extracted it.
Download the project template and place the files in the directory where you keep your project files.
After you have modified build.gradle.kts and sub.properties as needed, you can run tasks using gradlew.
For instance, to run the mux task for episode 03, navigate to the same directory as the build.gradle.kts file in a terminal, and run ./gradlew mux.03.
On Windows, simply run gradlew mux.03 from cmd.exe.
Note: The first time you run gradlew may take some time as Gradle as well as all dependencies need to be downloaded.
Future runs will be much quicker.
Manual
A detailed description of the different tasks, functions and classes provided can be found in the documentation.
Preliminaries
Defining tasks
Generally all your tasks will be configured inside the subs block in the build.gradle.kts file.
In order for any tasks to be generated, you must first define the episodes.
The episodes can be any valid string, and do not need to be numerical.
subs {
episodes("01", "02", "03", "OVA1", "OVA2")
}
You can now define a task:
subs {
episodes("01", "02", "03", "OVA1", "OVA2")
val myTask by task<DefaultSubTask> {
doLast {
println("This is task $currentTask for episode $episode.")
}
}
}
We have defined a group of tasks of type DefaultSubTask using the task function.
This will generate one task for each episode defined.
DefaultSubTask is a simple default type with no particular defined behavior.
We add a function to run when the task is executed using the doLast function.
We can list the resulting tasks using ./gradlew (or simply gradlew on Windows):
$ ./gradlew tasks --all
[...]
Other tasks
-----------
myTask.01
myTask.02
myTask.03
myTask.OVA1
myTask.OVA2
[...]
Note that the first time you run gradlew will take some time, as Gradle needs to download all dependencies and compile SubKt.
Future runs will be much faster.
We can now execute the task you defined, here for episode 02:
$ ./gradlew myTask.02
[...]
> Task :myTask.02
This is task myTask for episode 02.
BUILD SUCCESSFUL in 1s
1 actionable task: 1 executed
Depending on your mood, you may use any of the following equivalent syntaxes to define a
