航海日誌:副官換成 Cursor——把一首 MP3,一層一層蓋成一支會跳舞的 1080p 影片

副官這趟換成 Cursor。一個把 MP3 變成 1080p 影片的網頁工具,是怎麼從一張黑畫布、一層一層長出來的——先讓聲音變成畫面,再錄成影片,接著讓 AI 在瀏覽器裡聽寫字幕,最後由人校正那首被聽錯的粵語功夫歌。

分享
航海日誌:副官換成 Cursor——把一首 MP3,一層一層蓋成一支會跳舞的 1080p 影片
開發航海日誌| 船長 Balung · 這趟的副官:Cursor · 主題:mp3ToVideo

上一篇航海日誌,我和副官 Claude Code 把一座每天早上自己醒來的 AI 新聞站開出了港。這一篇,船上換了一位副官——Cursor,而我們要蓋的東西也完全不同:一個把一首 MP3,變成一支會跟著音樂跳舞的 1080p 影片的網頁工具。上傳歌、選一種視覺特效、讓 AI 自動聽寫出字幕、然後錄成影片下載。全部在瀏覽器裡完成,沒有後端。

先誠實講一件事,免得你誤會這篇的性質。這個專案不像上一艘船有完整的航海日誌可以一天一天倒帶——它在 GitHub 上其實只有「一次整包上傳」的紀錄。所以這篇不是「逐時的開發實錄」,而是一篇教學式的重建:我把這個工具拆成一層一層、照著它該被蓋出來的順序,重新講一遍——先讓聲音變成畫面,再把畫面錄成影片,接著讓 AI 聽寫字幕,最後由人校正。你會看到一個這樣的工具,是怎麼從一張黑畫布,一層一層長成成品的。

兩個角色照舊:船長是我這個人類,定方向、做取捨、把關品味;副官這趟是 Cursor——一個讀得懂規格、自己寫程式的 AI 編輯器。而這趟航行真正的主角,其實是一份文件:requirement.md

map

序章:先畫一張規格海圖(requirement.md)

上一艘船的開場白是「先讀文件,先別寫程式」。這一艘更極端:整趟航行就是繞著一份文件打轉。

我沒有對 Cursor 喊「幫我做一個音樂視覺化網站」就放手。我先和它一起,把要做的東西鉅細靡遺地寫成一份軟體需求規格書 requirement.md——這個工具該支援哪些檔案、20 種特效各自叫什麼名字、播放與錄製的每一個按鈕在什麼狀態下可不可按、AI 辨識有哪三種模式。寫到什麼程度?連「之後若要從零重做,請以這份文件為唯一依據」都白紙黑字寫進去了。

gl-req

為什麼要先花這個力氣?因為 requirement.md 就是這趟航行的海圖。對副官 Cursor 下的每一道指令,本質上都是同一句話:

船長:照 requirement.md 的這一節,把它實作出來。對使用者可見的行為,要跟文件對得起來。

有了這張海圖,副官寫出來的東西就不會跑偏;而我這個船長要做的,是決定這張海圖長什麼樣——以及,把整個工程切成「一次只蓋一層」的順序。接下來的每一章,就是一層。

第一章:先讓聲音變成畫面

這一層:上傳一首 MP3,讓它在畫面上「動起來」。

ch01

這個工具最核心的魔法,是把「聽得到、看不到」的聲音,變成「看得到」的畫面。我給副官的第一道指令,刻意不碰任何花俏的東西:

船長:先做最底層的引擎就好。用瀏覽器內建的 Web Audio API 把上傳的 MP3 解碼、播放,並即時取出「頻譜」——每個頻率有多強。再開一張 1920×1080 的畫布,把頻譜畫成最樸素的等化器長條。先讓一首歌能在畫面上跳起來,其餘的之後再說。
gl-webaudio

副官照著做了。第一版很樸素,但那個瞬間其實是整個專案的心臟——當我上傳第一首歌、按下播放,那排長條真的跟著節奏高低起伏:低音在左邊頂得老高,高音在右邊細細碎碎。聲音,第一次變成了看得見的東西。

圖:工具的控制台:左邊上傳檔案、選 AI 辨
圖:工具的控制台:左邊上傳檔案、選 AI 辨識方式,右邊選 20 種特效與參數,底下是那張等著被填滿的 1920×1080 黑畫布。

心臟一通電,剩下的就是「換皮」。我請副官把繪圖的部分抽象成一張對照表:每一種特效對應一個獨立的繪圖函式,由一個設定物件統一管理。這個架構決定,讓「再加一種特效」變成一件很便宜的事——於是 20 種特效,分成五大類,一個一個長了出來。

20 種特效節錄

從經典的等化柱、環形音波,到科技感的數據雨、霓虹脈衝;從流體的極光、粒子宇宙,到機械的低音齒輪;最後還有一整類「禪意」——水墨在宣紙上隨著音量游走的書法線條。同一首歌,換一種特效,就是完全不同的一支 MV。這背後是同一個道理:把骨架設計對,內容就能像家具一樣一件件搬進來。

第二章:把畫面錄成一支能下載的影片

ch02

畫面會跳了,但它只活在瀏覽器當下那一刻——關掉分頁就沒了。要讓它變成一支能傳給別人、能上傳到 YouTube 的東西,得把「正在動的畫面」連同「正在響的聲音」一起下來。

船長:把畫布的畫面用 captureStream 接成影像軌,再從音訊那邊拉一條音軌,兩條合在一起餵給 MediaRecorder,錄成 1080p、8 Mbps 的 WebM。播放結束就自動停錄、解鎖下載鈕。
gl-recorder

這裡撞到一道很容易被忽略的暗礁:暫停。使用者按下 Pause,如果只停了音樂,畫面卻還在動、錄影機還在錄,那錄出來的影片就會音畫不同步——畫面比聲音多跑了一截。所以「暫停」這個動作,必須同時叫停三件事:音訊、動畫迴圈、錄影機;按下 Resume 時,三者也要一起、從同一個時間點接回去。

船長:Pause 要把音訊 suspend、取消動畫迴圈、mediaRecorder.pause() ——三個一起停。Resume 再三個一起接回來,還要校正時間基準,讓字幕跟頻譜對得上。一個都不能漏。

這是這類「即時錄製」工具最隱形的細節:使用者只覺得「暫停就該全部暫停」,但對程式來說,那是三台不同的機器,要被同一個指令精準地一起踩煞車。

第三章:讓 AI 在瀏覽器裡,自己聽寫字幕

ch03

影片能錄了,但還缺一樣讓它變「作品」的東西:字幕。你當然可以自己上傳一份 SRT 字幕檔,但更酷的是——讓 AI 直接聽這首歌,自己把詞寫出來。

最關鍵的一個品味決定在這裡:要把 AI 放在哪裡跑?

船長:語音辨識不要送去任何雲端、不要叫使用者辦帳號貼金鑰。直接用 transformers.js,把 Whisper 這顆語音模型下載到使用者自己的瀏覽器裡跑。音訊一秒都不會離開他的電腦。
gl-whisper

這個決定的代價,是第一次使用會慢——瀏覽器得先從網路上把那顆 ONNX 模型(幾十到上百 MB)抓下來。但換來的是徹底的隱私:你的歌、你的錄音,從頭到尾留在你自己的機器上。對一個給大眾用的小工具來說,這種「資料不離身」的安心,值得那幾十秒的等待。

副官在實作時還處理了兩個魔鬼細節:Whisper 只吃 16 kHz 單聲道的音訊,所以餵進去前要先把歌重新取樣;而且它會盡量連每句話的起訖時間一起吐出來,這樣才能直接變成一條一條對時的字幕,而不是一大坨文字。

第四章:最深的暗礁——AI 把粵語功夫歌聽錯了

ch04

每一趟航行都有一道最深的暗礁。這一趟的,藏在一首歌裡。

我拿來測試的,是《少林足球》那首經典的「少林功夫好嘢」。一按下 AI 辨識,問題就來了——這是一首粵語歌,而 Whisper 預設很容易把中文當成英文、或把粵語硬聽成國語。第一版自動產生的字幕,錯得很有戲:

reef-diff

「無敵鐵頭功」被聽成「目的鐵頭公」、「一級棒」變成「一起吧」、粵語的「係好勁」變成「誰好景」。意思全跑了。

這道暗礁,其實是兩手並用才繞得過去——一半靠副官,一半靠船長。

副官那一半,是在規格裡留好一個逃生口:讓使用者能指定轉錄語言。我請它把「中文 chinese」設成預設,並在介面上明白寫「國語歌曲建議選這個」,因為放著自動偵測,十之八九會漂成英文。

船長:語言選單預設給我鎖在中文,旁邊註明白——這是把暗礁標進海圖,讓後面的人別再撞上去。

但船長這一半更關鍵,也更老實:AI 聽寫只是初稿,最後一定要有人校對。所以這個工具從設計上就接受「人來修」——你可以下載 AI 產生的 SRT、用眼睛和耳朵把「目的鐵頭公」改回「無敵鐵頭功」,而且萬一改壞了,還能一鍵還原成最初上傳的那份字幕。專案資料夾裡那兩個檔案——一份 caption-auto.srt(AI 聽的)、一份 caption-corrected.srt(人校的)——就是這道暗礁留下的航跡。

這也呼應了上一艘船教我的同一件事:AI 負責把又快又粗的初稿做出來,人負責守住最後那關品質。機器聽得飛快,但「無敵」還是「目的」,得由聽得懂這首歌的人來定。

第五章:合體——特效+聲音+字幕,按下錄製

ch05

四層都蓋好了,最後一章只做一件事:把它們疊在一起。

一首歌、一種特效、一份(校好的)字幕。在每一幀畫完特效之後,程式會依當下的播放秒數,挑出該顯示的那句字幕,用大字、加陰影,壓在畫面底部。然後按下那顆 Play & Record——聲音、畫面、字幕同時上場,一支 1080p 的影片就這樣錄了下來。

圖:成品的一幀:等化柱跟著音樂跳動,校正過的
圖:成品的一幀:等化柱跟著音樂跳動,校正過的字幕「少林功夫好耶」穩穩壓在底部——特效、聲音、字幕三層疊在一起。

看著等化柱跟著「少林功夫好耶」的節奏跳動、字幕穩穩壓在底下,我才真正感覺到:那一張最開始的黑畫布,一層一層,終於長成了一支完整的作品。錄製結束,下載鈕亮起,一支 .webm 落進資料夾。航行完成。

尾聲:規格是海圖,副官是引擎

這一艘船和上一艘最大的不同,是它沒有後端、沒有每天醒來的排程、沒有對外的金鑰——它整個就活在使用者自己的瀏覽器裡。但兩趟航行教我的事,其實是同一件:

真正掌舵的,從來不是某個 AI 工具,而是那張海圖。這一趟副官換成了 Cursor,但決定成品長什麼樣的,是那份 requirement.md——把要做什麼、每個按鈕怎麼動、暗礁在哪裡,先想清楚、寫下來。海圖畫得夠清楚,副官這具引擎就能把船開得又快又穩;海圖含糊,再強的引擎也只是快速地開往錯的方向。

而且這一趟還多教了我一件事:一個看起來很複雜的東西,幾乎都能被拆成「一次只蓋一層」。先讓聲音變成畫面,再把畫面錄成影片,接著讓 AI 聽寫,最後由人校正——每一層都單純到可以獨立完成、獨立驗收。複雜,是簡單一層一層疊出來的。

人定方向、畫海圖、守最後一關;AI 讀海圖、划槳、把一層一層蓋起來。這就是我這趟對 Vibe Coding 的體會。


📦 想自己玩玩看、或對照原件的:這個工具的完整程式碼與那份 requirement.md 規格海圖,都在 GitHub repo 裡(在自己電腦上以靜態伺服器開啟即可,別直接用 file://)👉 github.com/captain-balung/mp3ToVideo