Files
hoelee-blog/src/content/posts/zh/generating-subtitles-for-songs-without-timestamps.md
T
hoelee 0dae8b0daa
Deploy / build (push) Successful in 34s
fix(art): give both 2026-10-20 posts their artwork, and gate the build on it
The YuE2 and subtitles posts shipped text-only: render_from_art.mjs only READS
ogImage/banner, and after the 2026-10-19 bulk migration no script had to write
them, so both pages advertised /og-default.jpg and rendered no hero banner.

- new metaphors (in build_captions.py, frozen here via sync_prompts_catalog.py),
  captions, art-meta evidence, banner plan and the five derived files per slug
- frontmatter: ogImage + banner in EN and ZH for both posts
- scripts/check-assets.mjs is now step 1 of npm run build, so CI fails when a
  published post lacks a language-correct og card, banner, banner .webp or
  400/800 list thumb. It reported 16 problems on the unfixed tree, all here.
2026-10-20 23:37:52 +08:00

40 KiB
Raw Blame History

title, description, pubDate, category, tags, ogImage, banner, draft
title description pubDate category tags ogImage banner draft
给没有时间戳的歌曲自动生成字幕 AI 音乐工具只给你音频 —— 没有逐行时间,没有卡拉 OK。这是我给 18 首歌生成 LRC 与 SRT 字幕的完整流程,为什么单纯转写不行,以及让歌词框滚到底的两个 bug。 2026-10-20 tutorials
subtitles
lrc
srt
whisper
forced-alignment
ai-music
python
/og/zh-generating-subtitles-for-songs-without-timestamps.jpg /banners/generating-subtitles-for-songs-without-timestamps.jpg false

我试过的每一个 AI 音乐工具,交出来的东西形状都一样:一个音频文件。没有一个会告诉你每一句是什么时候唱的。只想听的话没问题,但只要你想把歌词放到屏幕上 —— 卡拉 OK 的那一行、视频字幕、播放器下面的滚动框 —— 就完全不够用。

时间轴才是全部难点。音频反而是简单的部分。

这是我最后跑通的一套流程,用在本地生成的 18 首英文歌上,外加它在浏览器里崩掉的两个地方。下面全部可复现:它唯一需要你提供的东西,是你已经写好的歌词。

你真正需要的两样东西

两种字幕格式,共用同一份数据结构:

格式 用在哪 长什么样
LRC 音乐播放器、卡拉 OK 块、<audio> 歌词层 [mm:ss.xx]歌词行
SRT VLC、YouTube、Premiere、ffmpeg 烧字 序号 / hh:mm:ss,mmm --> hh:mm:ss,mmm / 文本

两者本质都是「行 → 起始时间」(SRT 多一个结束时间)。所以真正的任务是: 给定一串歌词行和一个音频文件,找出每一行从哪儿开始。

为什么「直接转写」行不通

最直觉的做法:跑一次语音识别,把它的分段结果写出去。它会因为两个原因失败。

1. 唱腔的识别率远低于说话。 模型要同时对付乐器、混响和拉长的元音。我这一批里,中文唱词的词错率大约 25–35%,电子混音密集的更差。

2. 它会整段丢掉。 有一首 140 BPM 的燃曲,整段第一段主歌压根没出现在转写结果里 —— 人声被埋住了,模型直接跳过去。另外两首丢了尾奏。

有洞的转写,变不出没洞的字幕。但换个框法就全通了:

你不是在转写,你是在对齐。你早就知道确切的字 —— 那就是你的歌词。音频只是用来提供锚点。

让它成立的思路反转

让识别器给出词级时间戳,然后拿它的输出跟你已知的歌词做最长公共子序列 diff。你根本不在乎它有没有听对 —— 你在乎的是它听出来的那些词顺序对不对,因为每一个匹配都是一个时间锚点。

import difflib

ref = [u for line in lines for u in units(line)]      # 你的歌词
hyp = [w[0] for w in asr_words]                        # 它听到的
sm = difflib.SequenceMatcher(a=ref, b=hyp, autojunk=False)

for tag, i1, i2, j1, j2 in sm.get_opcodes():
    if tag != "equal":
        continue            # 不匹配的地方只是空洞,不是错误
    for k in range(i2 - i1):
        matched[ref_index[i1 + k]] = asr_words[j1 + k][1:]   # (起, 止)

然后每一行歌词取它第一个匹配单位的时间戳。这种做法的稳健性是转写给不了的:60–70% 的词准确率就完全够用,因为每一个位置正确的词都钉住了它所属的那一行。

这批实测的锚定覆盖率 —— 也就是「拿到了至少一个真实锚点」的行占比:

语言 单位 平均锚定覆盖率 全行锚定的歌
英文 词 0.96 9/18 首
中文 字 0.70 11/11 首

英文对齐更好,原因很无聊:词之间用空格隔开,所以每个 ASR token 能干净地对应一个歌词 token。中文则必须把每个字平摊到 token 的时间跨度上:

span = w.end - w.start
for i, ch in enumerate(hanzi_in_word):
    chars.append((ch, w.start + span * i / n, w.start + span * (i + 1) / n))

修正器:三条规则,挡住荒唐的时间

锚点能带你走完大半程。但如果放着不管,锚点之间的空隙会产出物理上不可能的时间 —— 其中两个是真的咬到我了:

故障 A —— 一行落在 0.0 秒。 有一首歌的第一句歌词正好落在零点,前奏都还没结束,因为锚点搜索在开头的喊麦里匹配到了一个零散的字。

故障 B —— 四行挤在 0.6 秒里。 有一首歌的尾奏被识别器整段漏掉,剩下的行被压到相隔 0.18 秒。12 个字不可能在 0.18 秒里唱完。

所以对齐之后,跑三条规则:

  1. 估计这首歌自己的速率 —— 用相邻两行都已锚定的样本(单位数 ÷ 秒数,取中位数)。每行的期望时长 = 单位数 ÷ 速率,下限约 1.8 秒。同一批里英文是 0.96 到 2.33 词/秒,中文是 2.3 到 4.1 字/秒,不能用全局常数。
  2. 按比例填充没锚定的段,而不是固定行距。一段夹在两个锚点之间,就按期望时长分摊;空间不够就等比压缩。
  3. 剔除不可能的锚点 + 强制最小行距。 起始时间早于「5 秒 + 它前面所有内容可能占用的时间」的锚点一定是假匹配 —— 降级重算。然后正向扫一遍,保证任意两行起始间隔不小于 1.2 秒;若尾部被推出音频,再从末尾倒推收回。

最后那趟扫描,才是把「看起来合理」变成「不可能闪烁」的关键。

同时输出两种格式

同一份时间数组,两个写出函数:

def lrc(ts, lines):                      # [mm:ss.xx]
    return "\n".join(f"[{m:02d}:{s:05.2f}]{l}" for (m, s), l in zip(ts, lines))

def srt(spans, lines):                   # hh:mm:ss,mmm
    out = []
    for i, (line, (a, b)) in enumerate(zip(lines, spans), 1):
        out.append(f"{i}\n{stamp(a)} --> {stamp(b)}\n{line}\n")
    return "\n".join(out)

LRC 只要起点;SRT 要起点和终点,所以用下一行的起点当作当前行的结束(这正是最小行距规则重要的原因 —— 没有它,SRT 块会变成零时长,有些播放器会整块跳过)。

验证:我错了两次的地方

字幕早在显示之前就是对的。两个 bug 都在那 20 行播放器脚本里,而且都是读代码看不出来的。

Bug 1 —— 一开播就在末尾。 找当前行我写的是 lines.findIndex(l => l.t > now) - 1。第一句之前 findIndex 返回 0,于是 i 变成 -1,而那句 if (i < 0) i = lines.length - 1 把「还没开始」翻译成了「最后一行」。播放器一打开就停在尾奏。

Bug 2 —— 然后第一个和弦就滚到底。 修完 bug 1,我用 p.offsetTop 来做居中。offsetTop 是相对最近的定位祖先量的 —— 而歌词框没有 position: relative,所以它返回的是这一行到页面顶部的距离,上千像素。scrollTop 被夹到 maxScroll:直接到底,字全看不见。

正确写法根本不关心定位上下文:

const br = box.getBoundingClientRect(), pr = p.getBoundingClientRect();
box.scrollTop += (pr.top - br.top) - (box.clientHeight - pr.height) / 2;

关于「怎么测」的教训比修复本身更重要。 我在 Node 里单测过索引逻辑 —— 通过了。我看过截图 —— 我把高亮的那一行看错了。两者都看不见布局 bug。真正有效的是在 headless Chrome 里打数值探针:stub 掉 currentTime,派发 timeupdate,然后把真实 DOM 读回来。

chrome --headless=new --dump-dom --virtual-time-budget=8000 \
       --window-size=760,520 "file:///probe.html"
Object.defineProperty(a, 'currentTime', { get: () => 100, configurable: true });
a.dispatchEvent(new Event('timeupdate'));
// 然后输出:当前行索引、它在框内的偏移、scrollTop、maxScroll

通过条件是数字,不是观点:当前行在框内的偏移应当约为框高的一半(我得到的 274 px 框里是 124 px);t = 0 时 scrollTop = 0 且没有任何行高亮;只有最后几句时 scrollTop = maxScroll。

结果

18 首歌,没有一首自带时间戳。现在每一首都有逐行的 LRC 和 SRT —— 只靠歌词加音频生成。

歌曲 18
字幕行数 342
平均锚定覆盖率 0.96
全行锚定的歌 9 / 18(每行都有时间)
处理耗时 每首 63–90 秒,纯 CPU
占用显卡 无

下面每个播放器都会滚动歌词、把当前句保持在中间,旁边还有 .srt 下载 —— 想烧字进视频或在 VLC 里打开都行。

<style> .subs-track{margin:2.4rem 0} .subs-track figcaption{display:flex;align-items:baseline;gap:.6rem;flex-wrap:wrap;font-weight:600;margin-bottom:.35rem} .subs-title{flex:1 1 auto;min-width:0} .subs-meta{font-weight:400;opacity:.6;font-size:.9em;white-space:nowrap} .subs-dl{margin-left:auto;flex:0 0 auto;font-size:.8em;font-weight:600;text-decoration:none;border:1px solid var(--accent,#22c55e);border-radius:6px;padding:.16rem .55rem;color:var(--accent,#22c55e);background:transparent;white-space:nowrap} .subs-dl:hover{background:rgba(34,197,94,.14)} .subs-dl::before{content:"↓ "} .subs-track audio{width:100%;max-width:560px} .subs-box{position:relative;max-height:250px;overflow-y:auto;border:1px solid var(--border,#3a3f47);border-radius:8px;padding:.7rem 1rem;margin-top:.6rem;font-size:.95em;line-height:1.6} .subs-box p{margin:.18rem 0;opacity:.55} .subs-box p.on{opacity:1;font-weight:600;color:var(--accent,#22c55e)} </style>
Tiny Borrowed Shoes · Celtic folk · 21 lines · 139 s.srt
[ti:Tiny Borrowed Shoes]
[ar:Mr Hoelee]
[00:11.56]Underneath the floorboards where the shadows play
[00:14.60]I tied a little ribbon at the break of day
[00:18.32]A button for a table, a thimble for a cup
[00:22.02]I climbs the ivy ladder when the sun comes up
[00:27.76]Sugar on the windowsill, a feather in my hand
[00:31.42]The garden is a jungle and a wonderland
[00:35.22]Careful not to wake the cat sleeping by the door
[00:40.00]Tip-toe in my borrowed shoes across the wooden floor
[00:44.46]Fly away, little breeze, carry me along
[00:48.10]Through the clover and the leaves, singing my own song
[00:51.90]Though the world is wide and tall, and I am very small
[00:57.58]In the secret morning light, I can have it all
[01:09.62]Dewdrops on the dandelion shining like a crown
[01:12.24]I catch a floating parachute and spin around and down
[01:15.98]If you hear a tiny laugh hiding in the wall
[01:19.62]That is just my secret song, answering your call
[01:27.26]Fly away, little breeze, carry me along
[01:30.72]Through the clover and the leaves, singing my own song
[01:34.58]Though the world is wide and tall, and I am very small
[01:40.32]In the secret morning light, I can have it all
[01:51.98]La-la-la, in the morning light...
Mist Over Skellig · Celtic folk · 18 lines · 193 s.srt
[ti:Mist Over Skellig]
[ar:Mr Hoelee]
[00:21.73]Where the gray Atlantic meets the abbey stone
[00:28.56]White birds circle high above the cliff alone
[00:33.56]Carved into the stairway of a thousand years
[00:42.16]Every drop of ocean rain whispers in my ears
[00:49.16]Sail into the silver mist, beyond the western sea
[00:57.96]Where the ancient lanterns glow for you and me
[01:06.32]Hush now, hear the harp strings ring across the foam
[01:14.74]Through the fog and falling stars, the tide will bring us home
[01:25.36]Candles in the chapel window flicker in the gale
[01:33.04]Ink upon the parchment tells a forgotten tale
[01:57.48]Little hands upon the rock, looking at the sky
[02:05.18]Watching all the centuries gently passing by
[02:16.98]Sail into the silver mist, beyond the western sea
[02:22.94]Where the ancient lanterns glow for you and me
[02:30.46]Hush now, hear the harp strings ring across the foam
[02:38.98]Through the fog and falling stars, the tide will bring us home
[02:57.02]Oceans rise and oceans fall...
[03:04.34]The tide will bring us home.
Willow by the Silver Stream · Celtic folk · 19 lines · 197 s.srt
[ti:Willow by the Silver Stream]
[ar:Mr Hoelee]
[00:23.83]Mmm-mmm, ahh-ahh...
[00:26.70]Silver runs the quiet stream beneath the willow tree
[00:33.64]Golden is the evening star shining down on me
[00:40.54]All the little meadow flowers fold their petals tight
[00:46.36]Waiting for the moon to weave a blanket of the night
[00:53.02]May the wind be gentle on your weary wings
[00:59.76]May you hear the lullaby the river softly sings
[01:06.28]Close your eyes and drift away where the shadows gleam
[01:13.52]Peace will walk beside you in the valley of a dream
[01:27.32]Fireflies are dancing slow among the reeds and moss
[01:33.14]Showing every wandering heart the stepping stones to cross
[01:39.46]Nothing in the dark can harm the light inside your chest
[01:46.66]Lay your head upon the grass and let the world rest
[01:53.62]May the wind be gentle on your weary wings
[01:59.76]May you hear the lullaby the river softly sings
[02:06.30]Close your eyes and drift away where the shadows gleam
[02:13.42]Peace will walk beside you in the valley of a dream
[02:50.86]In the valley of a dream...
[02:56.54]Sleep in peace tonight.
Clockwork Starlight · Japanese healing · 21 lines · 160 s.srt
[ti:Clockwork Starlight]
[ar:Mr Hoelee]
[00:19.30]Ticking in the quiet tower made of porcelain and glass
[00:23.44]I can see the painted clouds and paper swallows pass
[00:32.36]Every gear is turning slow beneath the velvet sky
[00:36.72]Counting all the shooting stars that never say goodbye
[00:46.12]Someone left a silver key beside the windowpane
[00:50.52]Unlocking all the memories hidden in the rain
[00:59.86]Sing for the dawn across the endless night
[01:04.66]Spinning the threads of shadow into light
[01:09.20]Even if the clock should stop and time fades away
[01:13.70]I will keep this little wish until the break of day
[01:22.28]La-la-lu-la, hear the distant bell
[01:31.34]Breaking the silent spell
[01:38.14]Pages of an ancient book are fluttering like wings
[01:42.50]Telling me of emerald seas and forgotten kings
[01:52.16]With my small and trembling hands I open up the door
[01:56.38]Stepping out into a world I've never seen before
[02:05.70]Sing for the dawn across the endless night
[02:10.56]Spinning the threads of shadow into light
[02:15.02]Even if the clock should stop and time fades away
[02:21.12]I will keep this little wish until the break of day
[02:33.64]Until the break of day...
Waltz of the Dandelion · Japanese healing · 18 lines · 179 s.srt
[ti:Waltz of the Dandelion]
[ar:Mr Hoelee]
[00:17.62]One, two, three, the summer breeze is knocking at my door
[00:22.56]Dancing with the sunlit dust upon the wooden floor
[00:30.62]Out across the grassy hill the dandelions grow
[00:38.40]Waiting for a little breath to tell them where to go
[00:48.08]La-la-la, wherever you may fly
[00:53.28]Underneath the big and gentle sky
[00:57.28]Even when the seasons change and winter turns to spring
[01:06.06]I will still remember every little song we sing
[01:27.36]Drops of rain upon a leaf are rolling into one
[01:34.62]Shining like a diamond in the afternoon sun
[01:42.46]If you ever lose your way or feel a little sad
[01:50.26]Think about the quiet days and all the smiles we had
[02:00.18]La-la-la, wherever you may fly
[02:05.28]Underneath the big and gentle sky
[02:09.14]Even when the seasons change and winter turns to spring
[02:17.90]I will still remember every little song we sing
[02:25.96]La-la-la, la-la-la...
[02:29.46]See you again someday.
Whale in the Milky Way · Japanese healing · 20 lines · 164 s.srt
[ti:Whale in the Milky Way]
[ar:Mr Hoelee]
[00:12.10]Ah-ah-ah, ooh-ooh...
[00:15.70]Up above the sleeping town where all the rooftops end
[00:21.86]Swims a giant silver whale that wants to be my friend
[00:27.94]Ripples in the Milky Way are washing on the shore
[00:34.34]Leaving little seashells by my bedroom door
[00:40.98]Can you hear the quiet song echoing in space?
[00:47.28]Every lonely little star has found a hiding place
[00:59.60]Float away, float away, into the velvet blue
[01:06.26]I am sending all my gentle lullabies to you
[01:12.74]Through the clouds of stardust and the galaxies afar
[01:19.59]You are never lost because I see how bright you are
[01:38.82]Paper boats upon the sea of infinite delight
[01:44.56]Carrying our tiny dreams across the quiet night
[01:51.08]Hold my hand and do not fear the deep and silent sky
[01:57.42]We can ride the silver whale and watch the world go by
[02:04.14]Float away, float away, into the velvet blue
[02:10.26]I am sending all my gentle lullabies to you
[02:16.74]Through the clouds of stardust and the galaxies afar
[02:23.52]You are never lost because I see how bright you are
[02:32.74]Goodnight, little star...
Puddle Jumping Parade · Japanese cute · 21 lines · 105 s.srt
[ti:Puddle Jumping Parade]
[ar:Mr Hoelee]
[00:06.76]One, two! Ready, go!
[00:08.68]Put on my yellow boots and grab my red umbrella too
[00:12.30]The rain has stopped and left the sky a shiny, happy blue!
[00:16.08]A giant puddle in the street is looking back at me
[00:20.08]I take a giant running leap—one, two, and three!
[00:23.86]Splash! Goes the water, jumping to the sky
[00:27.66]Even little sparrows stop to watch us marching by!
[00:32.66]Hooray, hooray! Join the happy parade!
[00:36.20]Dancing in the sunshine that the morning made!
[00:40.16]Clap your hands together, stomp your little feet
[00:44.44]Every single puddle makes the melody complete!
[00:49.30]La-la-la, step in time, smiling ear to ear!
[00:56.42]A snail upon the garden wall is waving with its horn
[01:00.10]The brightest, biggest rainbow in the neighborhood is born!
[01:03.90]We'll share a pocketful of sweets and sing a silly tune
[01:07.72]And march around the playground till we reach the afternoon!
[01:13.06]Hooray, hooray! Join the happy parade!
[01:16.24]Dancing in the sunshine that the morning made!
[01:20.12]Clap your hands together, stomp your little feet
[01:23.90]Every single puddle makes the melody complete!
[01:29.20]Jump! Splash! Hooray!
Strawberry Cloud Bakery · Japanese cute · 19 lines · 100 s.srt
[ti:Strawberry Cloud Bakery]
[ar:Mr Hoelee]
[00:08.32]Ding-dong rings the little bell above the bakery door
[00:13.48]Flour on the apron and confetti on the floor
[00:16.54]Butter in the mixing bowl and sugar in the pan
[00:20.34]Baking up the fluffiest creations that I can!
[00:24.20]Wait a little minute as the oven starts to glow
[00:29.12]Watch the little loaves of bread begin to rise and grow!
[00:34.04]Welcome to the bakery upon the strawberry cloud!
[00:39.24]Where singing in the kitchen is definitely allowed!
[00:42.76]A croissant for the puppy and a muffin for the cat
[00:46.20]With a dollop of vanilla cream upon a beret hat!
[00:57.82]Pour a cup of warm milk tea and sit beside the window
[01:01.68]Wave hello to everyone walking down below
[01:05.98]If your day is sour like a lemon on a tree
[01:09.78]Take a bite of honey toast and share a smile with me!
[01:13.90]Welcome to the bakery upon the strawberry cloud!
[01:19.26]Where singing in the kitchen is definitely allowed!
[01:22.48]A croissant for the puppy and a muffin for the cat
[01:26.20]With a dollop of vanilla cream upon a beret hat!
[01:33.58]Fresh out of the oven! Yum, yum!
Captain Marshmallow · Japanese cute · 22 lines · 95 s.srt
[ti:Captain Marshmallow]
[ar:Mr Hoelee]
[00:08.06]Attention please! Captain on the deck!
[00:10.64]Sofa is my pirate ship, the carpet is the sea
[00:13.92]Pillow fort is waiting there for teddy bear and me
[00:17.52]Put a paper triangle upon my curly head
[00:20.96]No one has to take a nap or go to sleep in bed!
[00:25.68]Left turn! Right turn! Watch out for the shark!
[00:31.08]Wait—that's just the fluffy dog barking in the dark!
[00:36.78]Aye, aye, Captain Marshmallow!
[00:38.42]Sailing where the candy breezes blow!
[00:42.04]Cookies in the treasure chest, chocolate on the map
[00:45.56]Give a little salute and a double-double clap! (Clap-clap!)
[00:50.74]We rule the living room today!
[00:56.34]Homework is forbidden by the royal pirate law
[00:59.60]Every single citizen must draw a dinosaur!
[01:03.12]More pudding for the afternoon, and juice inside the cup
[01:07.28]When the clock strikes dinner time, we'll eat the treasure up!
[01:13.80]Aye, aye, Captain Marshmallow!
[01:15.28]Sailing where the candy breezes blow!
[01:19.00]Cookies in the treasure chest, chocolate on the map
[01:22.46]Give a little salute and a double-double clap! (Clap-clap!)
[01:26.82]We rule the living room today!
[01:33.00]Mission accomplished! Yay!
Lanterns on the Lotus Pond · Guofeng fusion · 19 lines · 131 s.srt
[ti:Lanterns on the Lotus Pond]
[ar:Mr Hoelee]
[00:10.69]Patter goes the summer rain upon the lotus leaf
[00:15.29]Washing all the dusty world and bringing cool relief
[00:19.89]Stepping on the wet bluestone beside the willow bridge
[00:24.49]Dragonflies are resting on the painted wooden ridge
[00:28.58]Fold a little paper boat and light a candlewick
[00:33.16]Make a wish before the current carries it so quick
[00:40.46]Floating down the lotus pond, a hundred lanterns glow
[00:46.38]Mirrored in the ripples where the silver fishes go
[00:49.62]Listen to the bamboo flute across the misty stream
[00:54.22]In the courtyard of the moon, we share a summer dream
[01:03.96]Silk fan in my little hand to chase the fireflies
[01:08.72]Red string braided in my hair beneath the lantern skies
[01:13.36]Grandma tells a fairy tale of rabbits in the moon
[01:18.22]While the sweet osmanthus flowers blossom in the June
[01:25.54]Floating down the lotus pond, a hundred lanterns glow
[01:30.02]Mirrored in the ripples where the silver fishes go
[01:34.62]Listen to the bamboo flute across the misty stream
[01:39.36]In the courtyard of the moon, we share a summer dream
[02:06.76]Red lanterns drifting far away...
Wings of the Kunpeng · Guofeng fusion · 19 lines · 213 s.srt
[ti:Wings of the Kunpeng]
[ar:Mr Hoelee]
[00:25.18]Deep beneath the silent waves where no one ever sees
[00:31.60]Sleeps a tiny silver fish dreaming of the breeze
[00:38.16]Counting every falling tear that drops into the tide
[00:44.50]Waiting for the heavy gates of heaven to divide
[00:51.16]Do not fear the rising storm or shadows in the deep
[00:57.24]There is still a ancient promise that the ocean keeps
[01:06.34]Spread your wings and rise above the sea of clouds and rain
[01:13.18]Never let the mortal world bind you with a chain
[01:19.74]Fly across the mountain peaks where immortal rivers flow
[01:25.96]I will watch you turn to wind and let your spirit go
[01:49.10]Cycles of a thousand years are written in the stars
[01:54.96]Time will heal the broken shells and wash away the scars
[02:01.32]When the red camellia blooms upon the snowy shore
[02:07.58]We will meet beyond the sky and part ways nevermore
[02:16.86]Spread your wings and rise above the sea of clouds and rain
[02:23.52]Never let the mortal world bind you with a chain
[02:30.18]Fly across the mountain peaks where immortal rivers flow
[02:36.26]I will watch you turn to wind and let your spirit go
[02:48.84]Ah-ah-ah, fly into the wind...
The Puppeteer's Thread · Guofeng fusion · 19 lines · 221 s.srt
[ti:The Puppeteer's Thread]
[ar:Mr Hoelee]
[00:24.12]Carved from a willow branch, painted with a smile
[00:34.16]Dressed in tiny embroidered silk, traveling mile by mile
[00:46.00]Ten thin threads of crimson silk tied upon my hands
[00:55.50]Dancing in the market square across the distant lands
[01:07.80]Crowds will gather when the gong begins to softly ring
[01:13.12]Laughing at the little doll that cannot speak or sing
[01:21.66]Dance in the lantern light, spin around the stage
[01:26.70]Telling all the legends of a long-forgotten age
[01:32.42]Though my heart is made of wood and cannot feel the rain
[01:37.66]Every pull upon the thread remembers joy and pain
[02:10.68]Autumn leaves are falling down upon the empty street
[02:15.82]Only echoes remain of the applause so sweet
[02:21.36]Keep me in the wooden box beside your warm fireplace
[02:26.98]Even when the paint fades off my little smiling face
[02:36.74]Dance in the lantern light, spin around the stage
[02:42.86]Telling all the legends of a long-forgotten age
[02:48.56]Though my heart is made of wood and cannot feel the rain
[02:54.02]Every pull upon the thread remembers joy and pain
[03:05.04]Ten thin threads of crimson silk...
Paper Airplane in September · Acoustic folk · 18 lines · 128 s.srt
[ti:Paper Airplane in September]
[ar:Mr Hoelee]
[00:10.42]Sunlight on the wooden desk is moving inch by inch
[00:15.10]Sparrows on the telegraph wire never seem to flinch
[00:20.88]I fold a page of notebook paper creases nice and neat
[00:25.60]And throw it out the window to the quiet autumn street
[00:30.88]Fly away, paper plane, into the golden air
[00:36.36]Carry all the little words I didn't dare to share
[00:41.64]Goodbye to the summer clouds, hello to the breeze
[00:48.72]Floating past the bicycles and underneath the trees
[01:03.30]Chalk dust on the blackboard fades as afternoon goes by
[01:08.38]Clouds like cotton candy drift across the September sky
[01:13.76]Every simple little thing we took for granted then
[01:18.90]Stays inside a photograph we'll look at once again
[01:24.30]Fly away, paper plane, into the golden air
[01:29.66]Carry all the little words I didn't dare to share
[01:34.90]Goodbye to the summer clouds, hello to the breeze
[01:41.68]Floating past the bicycles and underneath the trees
[01:57.78]Underneath the autumn trees...
[02:02.18]Goodbye, summer days.
Porridge and Evening Smoke · Acoustic folk · 17 lines · 144 s.srt
[ti:Porridge and Evening Smoke]
[ar:Mr Hoelee]
[00:11.54]Smoke is rising from the roof into the purple sky
[00:17.04]While the evening swallows call and hurry on up high
[00:22.84]Steam upon the kitchen glass is blurring out the view
[00:28.48]Sitting on a wooden stool, I wait for dinner too
[00:34.02]One bowl of warm white rice, a dish of tender greens
[00:39.52]The simplest kind of happiness is what the evening means
[00:45.42]No matter how the outside world is noisy, cold, and wide
[00:50.98]The lamp upon the dining table keeps us warm inside
[01:09.84]Wrinkles on a gentle hand that smooths my messy hair
[01:14.88]Telling me to eat it all and sit up in my chair
[01:20.48]Years will pass like river water flowing to the sea
[01:26.26]But this quiet kitchen light will always stay with me
[01:33.04]One bowl of warm white rice, a dish of tender greens
[01:38.76]The simplest kind of happiness is what the evening means
[01:44.60]No matter how the outside world is noisy, cold, and wide
[01:50.14]The lamp upon the dining table keeps us warm inside
[01:57.40]The lamp is always on for you...
Green Train to the Sea · Acoustic folk · 17 lines · 108 s.srt
[ti:Green Train to the Sea]
[ar:Mr Hoelee]
[00:28.22]Clickety-clack upon the track, the green train starts to roll
[00:29.42]Leaving all the city gray behind the tunnel hole
[00:30.62]Forehead pressed against the glass, I watch the fields go by
[00:31.82]Golden wheat and windmills waving underneath the sky
[00:33.02]Take me where the spring wind blows across the open blue
[00:34.22]Ten miles of wildflowers blooming just for me and you
[00:38.36]Every station has a name I've never heard before
[00:42.68]Soon the scent of salty air will meet us at the shore
[00:57.62]Strangers sharing oranges and stories in the seat
[01:01.92]Sunlight dancing in the aisle around my dangling feet
[01:06.68]Postcards in my backpack waiting for a stamp and pen
[01:11.50]To tell the people back at home I'm coming back again
[01:16.24]Take me where the spring wind blows across the open blue
[01:21.14]Ten miles of wildflowers blooming just for me and you
[01:26.34]Every station has a name I've never heard before
[01:30.66]Soon the scent of salty air will meet us at the shore
[01:46.82]Look, there is the sea...
Sanctuary of the Dawn · Choral · 19 lines · 182 s.srt
[ti:Sanctuary of the Dawn]
[ar:Mr Hoelee]
[00:22.82]Ahh-ahh, sanctus, ahh...
[00:25.64]Shadows fade before the light that rises in the east
[00:31.74]Every troubled storm is stilled and every sorrow ceased
[00:37.76]Through the tall stained-glass windows morning beams descend
[00:44.12]Shining on a quiet road where broken hearts can mend
[00:50.56]Peace be with you, weary soul, lay your burden down
[00:58.74]Where the silver rivers flow beyond the sleeping town
[01:05.08]Lift your eyes unto the hills where golden angels sing
[01:11.46]Rest beneath the gentle shelter of a shining wing
[01:21.16]Like a bell across the valley ringing clear and true
[01:27.18]Every dawn is bringing back a promise made anew
[01:34.18]Walk into the quiet light and leave the dark behind
[01:40.06]There is grace in every breath for all of humankind
[01:48.40]Peace be with you, weary soul, lay your burden down
[01:54.64]Where the silver rivers flow beyond the sleeping town
[02:01.02]Lift your eyes unto the hills where golden angels sing
[02:07.48]Rest beneath the gentle shelter of a shining wing
[02:15.30]Peace be with you... forevermore.
[02:26.48]Amen, ahh...
Look Upon Your Path · Choral · 17 lines · 122 s.srt
[ti:Look Upon Your Path]
[ar:Mr Hoelee]
[00:09.64]Look upon the winding path where fallen leaves are blown
[00:14.64]Children walking hand in hand so no one is alone
[00:19.64]Even when the winter wind is cold against your face
[00:25.04]Every step you take today is leading to a place
[00:31.52]Shine your light into the dark, let your voice be heard
[00:36.60]Rising like the morning lark, a brave and little bird
[00:41.60]Though the night is long and deep, the stars are watching still
[00:46.68]Spring will wake the seeds asleep upon the quiet hill
[00:57.00]Write a song upon the wind for everyone who strays
[01:02.16]Hope is like a tiny spark that sets the sky ablaze
[01:07.48]Hold the hand beside you tight and never look below
[01:11.88]There are gardens waiting where the wildest rivers flow
[01:21.30]Shine your light into the dark, let your voice be heard
[01:26.36]Rising like the morning lark, a brave and little bird
[01:31.34]Though the night is long and deep, the stars are watching still
[01:36.44]Spring will wake the seeds asleep upon the quiet hill
[01:45.98]Look upon your path... the dawn is near.
Bells of the Winter Star · Choral · 18 lines · 177 s.srt
[ti:Bells of the Winter Star]
[ar:Mr Hoelee]
[00:08.50]Ding-dong, ding-dong, ring out the bells...
[00:18.20]Snowflakes falling from the sky in silence pure and white
[00:25.44]Dressing every cedar bough in robes of silver light
[00:34.56]Listen to the steeple bells across the frozen square
[00:43.42]Sending out a melody upon the midnight air
[00:52.92]Ring, silver bells, across the winter snow
[01:01.30]Tell all the world the star has begun to glow
[01:11.76]Gloria, gloria, hear the angels sing
[01:19.28]Peace on the earth this holy night will bring
[01:32.12]Candles in the frosted glass are shining warm and clear
[01:40.42]Guiding every traveler home at the end of year
[01:49.68]Every chime is like a prayer ascending to the sky
[01:58.26]Watched by all the quiet stars that never pass us by
[02:09.62]Ring, silver bells, across the winter snow
[02:18.22]Tell all the world the star has begun to glow
[02:28.00]Gloria, gloria, hear the angels sing
[02:37.14]Peace on the earth this holy night will bring
[02:49.30]Gloria... peace on the earth.
<script> // subs-track: scroll LRC in step with ; current line stays centred document.querySelectorAll('audio[data-lrc]').forEach((audio) => { const raw = document.getElementById(audio.dataset.lrc).textContent; const lines = raw.split('\n').map((l) => { const m = l.match(/\[(\d+):(\d+)(?:\.(\d+))?\](.*)/); return m ? { t: +m[1] * 60 + +m[2] + (+(m[3] || '0')) / 100, txt: m[4] } : null; }).filter(Boolean); const box = document.createElement('div'); box.className = 'subs-box'; lines.forEach((l) => { const p = document.createElement('p'); p.textContent = l.txt; box.appendChild(p); }); audio.parentNode.insertBefore(box, audio.nextSibling); let cur = -1; audio.addEventListener('timeupdate', () => { const now = audio.currentTime; // last line whose timestamp has passed; -1 before the first line starts let i = -1; for (let k = 0; k < lines.length; k++) { if (lines[k].t <= now) i = k; else break; } if (i !== cur) { if (cur >= 0) box.children[cur].classList.remove('on'); cur = i; if (cur >= 0) { const p = box.children[cur]; p.classList.add('on'); const br = box.getBoundingClientRect(), pr = p.getBoundingClientRect(); box.scrollTop += (pr.top - br.top) - (box.clientHeight - pr.height) / 2; } else { box.scrollTop = 0; } } }); }); </script>