我跟 AI 寫了一個切片服務:從私有化 S3 到「怎麼確保最後我還看得懂

我跟 AI 寫了一個切片服務:從私有化 S3 到「怎麼確保最後我還看得懂

2025-12-30

這不是一個為了炫技的 side project,而是一個從「沒得用」開始自幹的切片服務。從單畫質影片 + 單版圖片起步,到後來導入 AI 重構、處理多畫質轉檔、資源併發與 Redis queue,每一步都是為了讓這個系統能在私有化部署與現實流量下活得下去。真正的挑戰不是寫 ffmpeg,而是讓 AI 幫你寫完後,你還看得懂

這個切片服務的起點,其實一點都不浪漫。

它不是什麼 Side Project Showcase,也不是「我想驗證一個很潮的技術」。

起因非常現實:私人專案,需要私有化部署用 S3-compatible storage(MinIO),但系統裡沒有任何多媒體處理能力,也沒有任何開源的系統能解決我的問題

這問題翻成白話就是:沒得用,只好自己幹

一開始我就知道,這東西不能寫在主系統裡

在寫任何 ffmpeg 指令之前,我先做了一個決定:這一定是一個獨立的 service。

理由很簡單:

  • ffmpeg 是 CPU-heavy
  • 多媒體處理一定會炸
  • 炸的時候不能拖主系統一起死

所以這個服務從第一天開始就只有一個責任:你丟檔案來,我處理完,丟進 S3,回傳結果

不碰帳號、不碰權限、不碰業務邏輯

第一版:只有單畫質影片,跟單版圖片上傳

這一步我刻意壓到不能再小

單畫質影片切片

第一個完成的功能只有這樣:

  • 上傳影片
  • 不轉碼
  • 切成 HLS
  • 上傳到私有化 S3

ffmpeg 在這裡只做一件事:copy,不重編碼

CPU 壓力低、I/O 單純、流程直線

這一版的目的不是「功能完整」,而是:先確認整條 pipeline 是真的能跑的

單版圖片上傳

圖片更簡單:收檔 → 上傳

第一版甚至沒有 Sharp。

因為我很清楚一件事:在流程還不穩定的時候,加變數只會讓 debug 變痛苦。

第一版的 AI 用法:產 snippet,不碰架構

第一版不是不用 AI,而是用得非常克制。

AI 幫我的只有一件事:把我已經知道要怎麼做、但不想重查文件的部分寫快一點

例如:

  • ffmpeg HLS 的參數組合
  • MinIO / S3 upload 的寫法
  • Node.js stream / buffer 的處理方式

但整個專案的:

  • 流程
  • 邊界
  • 結構
  • 責任切分

全部都是我自己決定,架構非常土:

  • Controller
  • Service
  • ffmpeg 直接塞在 Service
  • Queue 是 in-memory
  • 併發限制寫死在 code 裡

典型的:「我現在一定看得懂」版本

到這裡為止,我才敢停下來想一件事

在只有「單畫質影片 + 單版圖片」的情況下,我問了自己一個問題:如果我現在開始加功能,這個專案半年後我還敢不敢動?

答案很誠實:不敢

於是我做了一個順序上很關鍵的決定:先重構,再加功能

重構階段:我把整個專案交給 AI,不是要它幫我加新東西

我對 AI 的要求很直接:請你以一個後端架構師的角色,幫我重構這個專案

注意這一步沒有任何新功能,只有結構、邊界、責任的重新整理

AI 把整個專案,從 MVC + Service 重構成一個微型 DDD

實際發生的事情包括:

  • Video / Image / Upload 拆成 domain
  • ffmpeg / sharp 被關進 domain service
  • Queue 抽成 interface
  • 原本的 in-memory queue 改成 Redis-friendly
  • 併發限制全部變成 config

這一步的價值不是「變高級」,是讓後面每一次加功能,都有地方放

重構完成後,才開始面對真正麻煩的需求

多畫質影片:第一個逼你對邊界負責的地方

需求一加上去,第一個問題馬上出現:如果原影片不是 1080p 呢?

  • 720p 要不要生 1080p?
  • 540p 要不要硬拉?
  • 240p 要不要假裝有 360p?

這不是 ffmpeg 問題,我最後定了一個原則:只向下轉碼,永不升畫質

實作流程很直接:

  1. ffprobe 讀原始高度
  2. 根據高度動態生成可用 profile
const profiles = [1080,720,540,360];
const available = profiles.filter(p => originalHeight >= p);

如果你丟 240p,我就只給 original,不好看,但不說謊

假並行:我真的讓 AI 寫過,也真的踩過

在重構後加功能時,我一度讓 AI 很自然地寫出:

Promise.all(available.map(p =>transcode(p)));

理論上沒錯,實際上:

  • CPU 被切碎
  • ffmpeg 偶發 exit
  • 檔案 I/O 開始互咬

最後還是回到那個很土、但很穩的解法:

for (const pof available) {
	awaittranscode(p);
}

一次一個,讓 ffmpeg 獨佔資源

順序跑,反而比並行快,而且不會炸

圖片 resize:尺寸不乾淨,才是常態

圖片也是在重構後才加進來的。

一加就會遇到這種東西:

484 × 230

然後你一定得回答:

  • 以寬還是高為準?
  • 要不要裁?
  • thumbnail 要不要填滿?
  • 比目標尺寸小的要不要放大?

Sharp 不會替你選,AI 也不會

我最後選了一組一致的規則:

  1. 永遠維持比例
  2. 超過目標尺寸才縮
  3. thumbnail 不裁、不填

最後一步:我才要求 AI 幫我寫文件,帶我 onboard

這一步我刻意放在最後。

因為如果你在架構、邊界還在變的時候寫文件,只是在浪費時間。

我對 AI 說:

「假設我是只會 CRUD 的後端

今天第一天接手這個專案

請你寫文件,帶我從零走完整個流程」

我要的是:

  • 從 upload 到 output 的完整流程
  • 每個 domain 在幹嘛
  • 哪些地方可以動,哪些地方不要碰
  • 為什麼這裡順序、那裡並行

這一步是在確認一件事:這個專案,最後還是不是我能接手的東西。

AI 最可怕的地方,不是寫得太快

真正的風險,從來不是 bug,也不是效能

而是這件事:你失去對系統的心理模型

當 AI 開始幫你補邏輯、補結構、補抽象,如果你只是一路點頭、一路 merge,有一天你會發現:

系統跑得很好,但你不敢動它

所以我後來給自己訂了一個很硬的規則,AI 可以寫邏輯、可以重構、可以優化,但 AI 必須能把它寫的東西解釋給我聽

這不是一個炫技的專案,是一個誠實的專案

ffmpeg 不是新東西。

Sharp 不是新東西。

Redis、S3 也都不是。

真正困難的,其實是這些選擇:

  • 要不要向上兼容
  • 要不要產生假畫質
  • 要不要在基礎設施層說謊
  • 要不要把方向盤整個交出去

如果你也在用 AI 寫程式,希望這個經驗能幫你少踩一個坑:

可以讓 AI 幫你寫,

但一定要確保,最後還看得懂這個系統的人,是你自己