Skip to content

Instantly share code, notes, and snippets.

@penbuvt
Created October 23, 2025 19:28
Show Gist options
  • Select an option

  • Save penbuvt/6179bf3fa9f1dfb40a6f5842558d28bb to your computer and use it in GitHub Desktop.

Select an option

Save penbuvt/6179bf3fa9f1dfb40a6f5842558d28bb to your computer and use it in GitHub Desktop.
Karaoke template for "Butter-Fly" ft. Nice Eggy & Kip Cintamani: https://youtu.be/t35Zu71p6og
%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%;;;; Penbu-standard vtuber karaoke clip k-template
%;;;; Eggy & Kip ver.
%;;;;
Karaoke template by Penbu VT, originally made for the karaoke duet edit
featuring Nice Eggy and Kip Cintamani singing "Butter-Fly" by Wada Kouji.
https://youtu.be/t35Zu71p6og
Written for aegsc v0.39.0:
https://github.com/butterfansubs/aegsc
Output compatible with The0x539's Templater:
https://github.com/The0x539/Aegisub-Scripts/blob/trunk/src/0x.KaraTemplater.moon
%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%;;; License
%;;;
%; SPDX-License-Identifier: MIT
MIT License
Copyright (c) 2025 Penbu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%;;; Preamble
%;;;
%[ code once
-- rounds n to 3 decimal places
function r(n)
return util.ftoa(n, 3)
end
-- rounds n (in milliseconds) to the nearest centisecond
function cs(n)
return math.floor(n / 10 + 0.5) * 10
end
%]
%! code once | outer_bord_size = 7.875
%[ code once
-- Video dimensions.
-- There's probably a way to get this from the script itself
vw = 1280
vh = 720
%]
%[ code once
-- 165 bpm
beat = 60 / 165 * 1000
lead_in = cs(beat)
-- Utility to shift a time back to its original position
-- after the line has been retimed to add lead-in
function t(n)
return n + lead_in
end
%]
%[ code once anystyle
speaker_colors = {
["Chat"] = "&H808080&",
["NiceEggy"] = "&H99E3FE&",
["KipCintamani"] = "&HC6D59B&"
}
function speaker_c(speaker)
return speaker:find(speaker)
and speaker_colors[speaker]
or "&H808080&"
end
%]
%[ code once
-- Tiresias Infofont has incorrect metrics
-- and causes extra space to appear when using orgline.left and orgline.right
adjustment_factor = 0.19
function calc_padding(left, right, factor)
return (right - left) * factor / 2
end
%]
%[ code line
padding = calc_padding(orgline.left, orgline.right, adjustment_factor)
left = orgline.left + padding - outer_bord_size
right = orgline.right - padding + outer_bord_size
-- The lead-out effect depends on the next line,
-- so gather info about the next line.
if not orgline.next then -- last line
continuous_end = false
next_start = orgline.end_time - orgline.start_time
next_right = right
elseif orgline.end_time == orgline.next.start_time then -- continuous with next
continuous_end = true
next_start = orgline.next.start_time - orgline.start_time
next_right = orgline.next.right
- calc_padding(orgline.next.left, orgline.next.right, adjustment_factor)
+ outer_bord_size
else -- not continuous
continuous_end = false
next_start = orgline.end_time - orgline.start_time + lead_in
next_right = right
end
-- Vertical clip boundaries by layer
y1 = {
0,
0,
orgline.middle,
0,
}
y2 = {
vh,
orgline.middle,
vh,
vh,
}
%]
%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%;;; Main template
%;;;
Layer 1: Pre-syl outer border
Layer 2: Post-syl outer outline, top half
Layer 3: Post-syl outer outline, bottom half
Layer 4: Foreground text (inner outline, fill, shadow)
%[ template line loop bg 3 @1
!relayer($loop_bg)!
{
\pos(!orgline.x!,!orgline.y!)
\an2
\be1
\bord!outer_bord_size!
\shad0
\1c!speaker_c("")!
\3c!speaker_c("")!
}
%]
%[ template line @4
{
\pos(!orgline.x!,!orgline.y!)
\an2
\bord3.125
\3c&H000000&
}
%]
%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%;;; Lead-in
%;;;
The lead-in effect is a 1-beat long wipe from left to right, timed
so that the line is fully visible at the start time of the line.
%! mixin line | !retime("line", -lead_in, 0)!
%[ mixin line layer 1
{
\clip(
!r(left - outer_bord_size)!,!y1[line.layer]!,
!r(right) - vw!,!y2[line.layer]!
)
\t(0,!lead_in!,
\clip(
!r(left - outer_bord_size)!,!y1[line.layer]!,
!r(right)!,!y2[line.layer]!
)
)
\t(!lead_in!,!lead_in * 2!,
\clip(
!r(left - outer_bord_size)!,!y1[line.layer]!,
!r(right) + vw!,!y2[line.layer]!
)
)}
%]
%[ mixin line layer 4
{
\clip(
!r(next_right) - vw!,!y1[line.layer]!,
!r(right) - vw!,!y2[line.layer]!
)
\t(0,!lead_in!,
\clip(
!r(next_right) - vw!,!y1[line.layer]!,
!r(right)!,!y2[line.layer]!
)
)
\t(!lead_in!,!lead_in * 2!,
\clip(
!r(next_right) - vw!,!y1[line.layer]!,
!r(right) + vw!,!y2[line.layer]!
)
)}
%]
%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%;;; syl effect
%;;;
The syl effect is similar to \kf, acting only on the outer outline.
The foreground text transitions from 50% opacity to 100% opacity by syl,
taking the duration of a 16th note to complete.
%;; Removing the pre-syl outer outline, \kf-style
%[ mixin syl prefix layer 1
{
\t(!t(syl.start_time)!,!t(syl.end_time)!,
\clip(
!r(left + outer_bord_size * 2 + syl.right * (1 - adjustment_factor))!,!y1[line.layer]!,
!r(right)!,!y2[line.layer]!
)
)
}
%]
%;; Uncovering the post-syl outlines, \kf-style
%[ mixin line prefix layer 2 layer 3
{
\clip(
!r(next_right) - vw!,!y1[line.layer]!,
!r(left - outer_bord_size)!,!y2[line.layer]!
)
}
%]
%[ mixin syl prefix layer 2 layer 3
{
\t(!t(syl.start_time)!,!t(syl.end_time)!,
\clip(
!r(next_right) - vw!,!y1[line.layer]!,
!r(left + outer_bord_size * 2 + syl.right * (1 - adjustment_factor))!,!y2[line.layer]!
)
)
}
%]
%;; Post-syl colours
If only one singer is singing, both halves have the same colour.
When both singers are singing, each half has a different colour.
%[ mixin line layer 2 layer 3 actor NiceEggy actor KipCintamani
{
\1c!speaker_c(orgline.actor)!
\3c!speaker_c(orgline.actor)!
}
%]
%[ mixin line layer 2 actor Both
{
\1c!speaker_c("NiceEggy")!
\3c!speaker_c("NiceEggy")!
}
%]
%[ mixin line layer 3 actor Both
{
\1c!speaker_c("KipCintamani")!
\3c!speaker_c("KipCintamani")!
}
%]
%;; Foreground syl effect
%[ mixin syl layer 4
{
\shad0
\1a&H80&
\3a&H80&
\4a&HFF&
\t(!t(syl.start_time)!,!t(syl.start_time) + r(beat / 4)!,
\1a&H00&
\3a&H00&
\4a&H96&
\shad0.625
)
}
%]
%;; Fall effect
Special effect for when Kip stumbles on one of the lines:
the rest of the line falls away when the marked syl ends.
Implemented as the \-fall syl fx.
%[ mixin syl sylfx fall
{
\t(!t(syl.end_time)!,!t(syl.end_time + lead_in)!,4,
\frz-90
)
}
%]
%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%;;; Lead-out
%;;;
The lead-out effect is a wipe matching the position and timing of the
lead-in of the next line.
%; The0x539's Templater at commit dcee98d does not have `mixin line suffix`,
%; so work around it by hooking into the last syl instead.
%; This is important because the final value for a tag transformed by \t
%; is solely determined by the last \t affecting that tag regardless of its timing,
%; so the \clip with the final coordinates MUST be last.
%[ code once
function last_syl()
return not syl.next
end
%]
%; Extend the end to accomodate the lead-out effect
%! mixin line | !retime("delta", 0, lead_in)!
%[ mixin syl prefix if last_syl \
layer 2 layer 3 layer 4 %; layer 1 is ignored to avoid issues with stacked \t effects.
%; It should already be fully invisible by the time the wipe catches up.
{
\t(!t(next_start - lead_in)!,!t(next_start)!,
\clip(
!r(next_right)!,!y1[line.layer]!,
!r(right)!,!y2[line.layer]!
)
)
\t(!t(next_start)!,!t(next_start + lead_in)!,
\clip(
!r(next_right) + vw!,!y1[line.layer]!,
!r(right) + vw!,!y2[line.layer]!
)
)}
%]
%;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
%;;; Speech
%;;;
The speech style is the post-syl style but without any fancy animations
or retimes. Chat lines read out by the streamer are italicized.
%!!set-defaults, $ Speech
%[ template line
{
\pos(!orgline.x!,!orgline.y!)
\an2
\be1
\bord!outer_bord_size!
\shad0
\1c!speaker_c(orgline.actor)!
\3c!speaker_c(orgline.actor)!
}
%]
%[ template line @1
{
\pos(!orgline.x!,!orgline.y!)
\an2
\bord3.125
\3c&H000000&
}
%]
%! mixin line actor Chat | {\i1}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment