Reputation: 106
Is it possible to have rounded corners with the BorderStyle 4 in .ass (Advanced Substation Alpha)? I only found out that the BorderStyle 4 exists, because I was looking at this stackoverflow. Is there any good and complete documentation of the Advanced Substation Alpha format?
I'm currently using the following configuration:
BorderStyle=4 Outline=10
Upvotes: 4
Views: 1128
Reputation: 296
You need to use software called Aegisub and install dependency called ILL Shapery. It's recommended to download it via DepCtrl (Dependency Control), but dependency manager isn't built-in into standard Aegisub (available at aegisub.org), so I recommend installing an Arch's fork that has DepCtrl built-in (if you're running Windows).
When you open Aegisub you need to download ILL Shapery like so:
Automation
tab (it is located at top)DependencyControl
and click Install Script
option (loading can take a while)Automation Scripts
field, select Shapery v2.5.6
(or any current version) and leave Modules
field emptyOK
and after it installs click Close
Automation
tab and Automation...
field (on top with blue icon)Automation Manager
click Rescan Autoload Dir
and then close this pop-up windowNow select all subtitles and duplicate them (ctrl+A, right click, click on Duplicate
) and the duplicated lines will be selected, so for now mark all duplicate lines as "comments" (so that they still appear in script but won't show on screen, but will allow us later show text when we finish our rounded corners in border).
Select all uncommented (visible) lines and remove their border (click Alt+3 and change alpha
to 255) and shadow (click Alt+4 and change alpha
to 255), and lastly change their primary color (Alt+1) to black (&H000000&).
Corners
Now you can uncomment initial lines (those containing text) and change alpha
of their border and shadow to 255. And change layer number of those lines with text to 10 or higher (just to be sure that they're displayed above border).
You should be left with something like this:
Scale and rounding of corners might be different due to the fact of using different PlayResX
and PlayResY
headers in [ScriptInfo]
section.
It's almost perfect, it just is slighty misaligned visually in y-axis (in my opinion), so we need to position border about 1-2px higher.
To do it you need to select lines that have make borders (they have vector drawing instead of text) and while selecting them do:
OK
And now you should have the effect you wanted to achive:
Note: To make process easier you can 'chain' macros using AegisubChain macro and then only call single macro (instead of bunch of them) to do everything.
To achieve same thing using code, you need to use Aegisub-cli . Below are step-by-step instructions for setting it up on Windows and Linux (Ubuntu).
Download the Aegisub installer for Windows (Windows.MSVC.Release.wx.master.-.installer.zip
) from the latest release (currently 'feature 12').
Download the prebuilt binary of Aegisub-cli from the latest release.
Place aegisub-cli.exe
in the path/to/aegisub/Aegisub
directory, right next to Aegisub.exe
. So by default it will be C:\Program Files\Aegisub\aegisub-cli.exe
.
# Install necessary dependencies
sudo apt-get update && sudo apt-get install -y \
python3-pip git cmake pkg-config ninja-build build-essential \
libx11-dev libwxgtk3.0-gtk3-dev libfreetype6-dev pkg-config \
libfontconfig1-dev libass-dev libasound2-dev libffms2-dev intltool \
libboost-all-dev libhunspell-dev libuchardet-dev libpulse-dev \
libopenal-dev libxxhash-dev nasm liblua5.1-0-dev luarocks \
libcurl4-gnutls-dev
# Install Meson
python3 -m pip install --upgrade pip setuptools
sudo pip3 install meson
export PATH="$PATH:~/.local/bin"
# Build Arch1t3cht's Aegisub
cd ~
git clone https://github.com/arch1t3cht/Aegisub.git -b feature_12
cd Aegisub/
meson setup build --prefix=/usr --buildtype=release
meson compile -C build
cd build
sudo ninja install
# Set up `Automation 4` modules for Aegisub
mkdir -p ~/.aegisub/automation
cd ~/.aegisub/automation
git clone https://github.com/TypesettingTools/DependencyControl.git
git clone https://github.com/TypesettingTools/YUtils.git
git clone https://github.com/arch1t3cht/ffi-experiments.git
cd DependencyControl
git checkout v0.6.3-alpha
cd ../ffi-experiments
sudo luarocks install moonscript
meson setup build -Ddefault_library=static
meson compile -C build
cd ..
mkdir -p autoload include/l0 include/BM/BadMutex include/PT/PreciseTimer include/DM/DownloadManager
mv DependencyControl/modules/DependencyControl DependencyControl/modules/DependencyControl.moon include/l0/
mv DependencyControl/macros/l0.DependencyControl.Toolbox.moon autoload/
mv YUtils/src/Yutils.lua include/Yutils.lua
mv ffi-experiments/build/requireffi include/
mv ffi-experiments/build/bad-mutex/libBadMutex.so* include/BM/BadMutex/
mv ffi-experiments/build/bad-mutex/BadMutex.lua include/BM/
mv ffi-experiments/build/precise-timer/libPreciseTimer.so* include/PT/PreciseTimer/
mv ffi-experiments/build/precise-timer/PreciseTimer.lua include/PT/
mv ffi-experiments/build/download-manager/libDownloadManager.so* include/DM/DownloadManager/
mv ffi-experiments/build/download-manager/DownloadManager.lua include/DM/
rm -rf DependencyControl/ ffi-experiments/ YUtils/
sudo luarocks install luajson
# Install Aegisub-cli
cd ~
git clone https://github.com/Myaamori/aegisub-cli
cd aegisub-cli
python3 -m pip install meson==0.62 # Downgrade Meson due to sandbox violation (known issue)
meson --prefix=/usr --buildtype=release build
meson compile -C build src/aegisub-cli
sudo mv build/src/aegisub-cli /usr/bin/
sudo mv build/src/libresrc/libresrc.a /usr/lib/
sudo mv build/src/libresrc/default_config.h /usr/include/
ILL
and Clipper
modules (by downloading Shapery
, which will resolve them automatically)Open Aegisub and do the following:
Automation
tab (it is located at top)DependencyControl
and click Install Script
option (loading can take a while)Automation Scripts
field, select Shapery v2.5.6
(or any current version) and leave Modules
field emptyOK
and after it installs click Close
Automation
tab and Automation...
field (on top with blue icon)Automation Manager
click Rescan Autoload Dir
and then close this pop-up windowI created a simple macro using the ILL
module. To use it, you need to create a file named jz.RoundedBorders.lua
in the/Aegisub/automation/autoload
directory (macros in this location are automatically loaded when Aegisub starts). The default paths for this directory are:
~/.aegisub/automation/autoload/jz.RoundedBorders.lua
C:\Program Files\Aegisub\automation\autoload\jz.RoundedBorders.lua
And then paste the following Lua script:
script_name = "Create Rounded Border"
script_description = "Creates rounded borders for selected subtitles"
script_version = "0.1.0"
script_author = "Jukizuka"
local haveDepCtrl, DependencyControl = pcall(require, "l0.DependencyControl")
if haveDepCtrl then
local depCtrl = DependencyControl({
feed = "https://raw.githubusercontent.com/TypesettingTools/ILL-Aegisub-Scripts/main/DependencyControl.json",
{ "ILL.ILL" }
})
ILL = depCtrl:requireModules()
else
ILL = require("ILL.ILL")
end
local Ass, Line, Path, Table = ILL.Ass, ILL.Line, ILL.Path, ILL.Table
function CreateRoundedBorders(bboxOffset, roundingRadius, transformY, borderColor, borderAlpha)
local miterLimit, arcTolerance = 2, 0.25
return function(sub, sel, activeLine)
local ass = Ass(sub, sel, activeLine, true)
for line, s, i, n in ass:iterSel() do
ass:progressLine(s, i, n)
Line.extend(ass, line) -- Populate line table
-- Create top subtitle layer (text)
local topLayer = Table.copy(line)
topLayer.layer = topLayer.layer + 1
ass:setLine(topLayer, s)
-- Create bottom subtitle layer (rounded border)
Line.callBackExpand(ass, line, nil, function(line)
local bottomLayer = Table.copy(line)
-- Create bounding box
local boundingBox = Path(bottomLayer.shape):boundingBox()["assDraw"]
local extendedBoundingBox = Path(boundingBox):offset(bboxOffset, "miter", "polygon", miterLimit, arcTolerance)
-- Round bounding box and move it downward
local roundedPath = Path.RoundingPath(
extendedBoundingBox:export(), roundingRadius, false, "Rounded", "Absolute"
):move(0, transformY)
-- Set shape and border color
bottomLayer.shape = roundedPath:export()
bottomLayer.tags:insert({ { "c", borderColor } })
bottomLayer.tags:insert({ { "1a", borderAlpha } })
return ass:insertLine(bottomLayer, s)
end)
end
return ass:getNewSelection()
end
end
function Gui(sub, sel, activeLine)
local dialogConfig =
{
{ x = 0, y = 0, width = 1, height = 1, class = "label", label = "Offset: " },
{ x = 1, y = 0, width = 1, height = 1, class = "intedit", name = "offset" },
{ x = 0, y = 1, width = 1, height = 1, class = "label", label = "Radius: " },
{ x = 1, y = 1, width = 1, height = 1, class = "intedit", name = "radius" },
{ x = 0, y = 2, width = 1, height = 1, class = "label", label = "Transform Y: " },
{ x = 1, y = 2, width = 1, height = 1, class = "intedit", name = "transformY" },
{ x = 0, y = 3, width = 1, height = 1, class = "label", label = "Border Color: " },
{ x = 1, y = 3, width = 1, height = 1, class = "textbox", name = "borderColor", text = "&H000000&" },
{ x = 0, y = 3, width = 1, height = 1, class = "label", label = "Border Alpha: " },
{ x = 1, y = 3, width = 1, height = 1, class = "textbox", name = "borderAlpha", text = "&H00&" }
}
local pressed, res = aegisub.dialog.display(dialogConfig)
if not pressed then aegisub.cancel() end
return CreateRoundedBorders(res.offset, res.radius, res.transformY, res.borderColor, res.borderAlpha)(sub, sel, activeLine)
end
aegisub.register_macro(script_name, script_description, Gui)
aegisub-cli.exe --dialog "{\"button\": 0, \"values\": {\"offset\": 20, \"radius\": 20, \"transformY\": -1, \"borderColor\": \"&H000000&\", \"borderAlpha": \"&H00\"}}" --automation jz.RoundedBorders.lua input.ass output.ass "Create Rounded Border"
aegisub-cli --dialog '{"button": 0, "values": {"offset": 20, "radius": 20, "transformY": -1, "borderColor": "&H000000&", "borderAlpha": "&H00"}}' --automation jz.RoundedBorders.lua input.ass output.ass "Create Rounded Border"
import json
import subprocess
import shutil
aegicli_path = shutil.which("aegisub-cli")
bboxOffset = 20
roundingRadius = 20
yTransform = -1
border_color = "#000000"
border_alpha = 255
input_file = "input.ass"
output_file = "output.ass"
def ssa_bgr(color: str) -> str:
r, g, b = bytes.fromhex(color.lstrip("#"))
return f"&H{b:02X}{g:02X}{r:02X}&"
def ssa_alpha(alpha: int) -> str:
return f"&H{255 - alpha:02X}"
dialog_json = json.dumps(
{"button": 0,
"values": {
"offset": 20,
"radius": 20,
"transformY": -1,
"borderColor": ssa_bgr(border_color),
"borderAlpha": ssa_alpha(border_alpha),
}}
)
args = [
aegicli_path,
"--dialog", dialog_json,
"--automation", "jz.RoundedBorders.lua",
input_file, output_file,
"Create Rounded Border"
]
subprocess.run(args)
For example, if there are 5 lines to be processed, the output should look like this:
./src/auto4_lua_dialog.cpp (397): I 13:23:19.996 <agi/auto4_lua_dialog > [Automation4::LuaDialog::LuaReadBack] Pushing OK
alog::LuaReadBack] Pushing OK
../src/dialog_progress.cpp (52): I 13:24:57.255 <agi/dialog_progress > [DialogProgressSink::SetProgress] Progress: 20%
../src/dialog_progress.cpp (52): I 13:24:57.260 <agi/dialog_progress > [DialogProgressSink::SetProgress] Progress: 40%
../src/dialog_progress.cpp (52): I 13:24:57.263 <agi/dialog_progress > [DialogProgressSink::SetProgress] Progress: 60%
../src/dialog_progress.cpp (52): I 13:24:57.266 <agi/dialog_progress > [DialogProgressSink::SetProgress] Progress: 80%
../src/dialog_progress.cpp (52): I 13:24:57.270 <agi/dialog_progress > [DialogProgressSink::SetProgress] Progress: 100%
Upvotes: 1