Cyber Booth
由 touchdesigner 擴展的模組化拍貼機
by jx06t
Cyber Booth 是一款專為實體活動設計的互動拍貼系統。打破傳統靜態拍照的限制,結合了強大的影像處理引擎與直覺的操作介面,為參與者帶來充滿科技感與創意的專屬回憶。 Cyber Booth 採用了兼具效能與擴充性的模組化設計,透過獨立的配置文件(manifest.json)與視覺元件,無論是更換全新的互動特效、調整照片槽位排版 (Layout),或是新增動態文字與浮水印,都能以「抽換模組」的方式快速部署。
Cyber Booth — 互動拍貼系統
v3.4.0 · TouchDesigner + Node.js + Sharp + Cloudflare R2 + Supabase + Vercel
這是什麼
Cyber Booth 是一套結合高性能視覺引擎的互動拍貼機系統,專為實體活動設計。
攝影機畫面進入 TouchDesigner 後,即時套用長曝光累積、仙女棒粒子軌跡等特效,再透過 NDI 虛擬攝影機串流到瀏覽器操作介面——使用者看到的預覽畫面,就是最終合成照片的效果。拍攝完成後,Node.js 背景執行 Sharp 影像引擎,自動裁切對齊四張照片、疊加相框、插入動態日期與 QR code,輸出成品。整個流程從按下快門到成品上雲,完全自動化。
因為一開始這只是個偏概念性的小 demo 就隨便寫,但後來想要維護時又太忙所以就直接無腦叫 ai 重構,但總之重構的不是很好所以現在有點半屎山狀態。
本介紹文章內的部分通訊流程、配置方法、所需設備等段落為舊版本之敘述,將盡快更新。
核心功能
即時視覺特效
仙女棒粒子軌跡、長曝光幀疊加,完全由 TouchDesigner Feedback TOP 管理,Node.js 不介入幀邏輯。
自訂相框佈局
透過 manifest.json 配置畫布尺寸、照片槽位、文字 widget 與 QR code,無需修改程式碼。支援多個 layout variant,現場可即時切換。
手機遙控面板
同網域下手機訪問 /remote,即可控制快門與選片,無需觸碰主機。桌面與手機介面共享同一套狀態機,透過 Socket.io 即時同步。
私有雲端取圖
整合 Cloudflare R2(媒體儲存)與 Supabase(資料庫)、Vercel(下載頁面)。Session ID 加密混淆防止他人掃描,檔案 24 小時後自動清理。支援照片與影片上傳。
提前結束補齊
拍不足四張可隨時停止,系統自動循環補齊至四張,保證輸出完整的四格拍貼成品。
多模組 / 多佈局切換
在 modules/ 中放置不同視覺主題(各含獨立 .tox 特效與 manifest.json),現場可即時切換,無需重啟服務。
系統架構
系統有兩條獨立的資料流,控制與視訊互不干擾:
控制流:Browser ↔ Node.js ↔ TouchDesigner,透過 Socket.io(雙向狀態同步)與 HTTP REST(指令下發)協作。
視訊流:攝影機 → TD 特效處理 → NDI Out → NDI 6 Webcam 虛擬驅動 → 瀏覽器 getUserMedia,單向傳遞,瀏覽器預覽的畫面即已包含所有即時特效。
存檔流程由 TD 主動觸發:TD 完成存檔後以 POST /td_state_update 通知 Node.js 進入 REVIEWING 狀態,附帶 currentFile 檔名,Node.js 附加 previewUrl 後廣播給所有前端。
螢幕截圖




拍攝範例圖
拍攝狀態機
Node.js 持有 currentSystemState 作為唯一狀態來源,透過 status_update Socket 事件廣播給所有連線裝置。
| State | 名稱 | 說明 |
|---|---|---|
2 |
IDLE | 待機,等待觸發快門 |
3 |
COUNTDOWN | 3 秒倒數,Node.js 以 setTimeout 管理 |
0 |
RECORDING | 長曝光快門開啟,TD Feedback TOP 開始幀疊加 |
1 |
PROCESSING | 等待 TD 存檔,或 Sharp 影像合成進行中 |
4 |
REVIEWING | 顯示預覽圖,等待使用者 KEEP / RETAKE |
5 |
FINISHED | 合成完成,顯示成品與 QR code |
狀態流轉:
IDLE → trigger_shot → COUNTDOWN → 3s →
├─ recording → RECORDING → STOP → PROCESSING → REVIEWING
└─ snapshot → PROCESSING → REVIEWING
REVIEWING → KEEP (×4 or finish_early) → PROCESSING → FINISHED
REVIEWING → RETAKE → IDLE
兩種拍攝模式:
- 錄影模式(recording):倒數後快門開啟,經過設定秒數或使用者手動按 STOP 結束,TD 凍結畫面並存檔。
- 快拍模式(snapshot):倒數後 TD 自動擷取當前幀存檔,不經過 state 0。
若模組支援多個模式可在同一個 layout 中自由組合,以 layout 作為使用者可選擇的基本單位(例如:若要提供兩種模式讓使用者選擇,須建立兩個 layout)。
影像合成
合成引擎 composer.js 以 Promise.all 並行裁切四張照片,依 manifest 疊加相框 overlay,渲染動態文字與 QR code widget,最後只執行一次 Sharp pipeline 輸出(toBuffer() → 寫檔),效能最大化。
layout 物件由 server.js 的 activeLayout 傳入,overlay_path 在載入時即已轉換為絕對路徑,合成引擎不處理路徑解析。
Widget 支援類型:
type: "text":靜態字串或動態佔位符({CURRENT_DATE})type: "image":靜態圖片路徑(如 Logo)或動態生成({QR_CODE})
快速上手
安裝需求: TouchDesigner 2023+、Node.js LTS、NDI 6 Tools、攝影機輸入源(WebCam / DSLR 採集卡 / VDO.ninja)。
啟動步驟:
- 開啟
TD/main.toe,確認影像輸入源與 NDI 輸出正確,Web Server DAT(Port 8080)已啟動。 - 開啟 NDI 6 Webcam 工具,於 Video 1 選擇 TouchDesigner 作為輸入來源。
- 執行
npm install,再執行npm run start,伺服器監聽 Port 5000。 - 瀏覽器訪問
http://localhost:5000開始拍攝;手機遙控訪問http://<本機IP>:5000/remote。
所有拍攝照片與合成成品預設存放於 sessions/ 資料夾,不會上傳至公開雲端。
自訂相框佈局
參考 modules/cyber_standard/manifest.json,複製後修改。在 server.js 的 loadModuleManifest() 中指定模組名稱即可套用。
{
"name": "Dither",
"preview_image": "preview.jpg",
"capabilities": {
"capture": {
"modes": [
"instant",
"timed"
],
"timedDurations": [
3
]
},
"output": {
"types": [
"image",
"video"
]
}
},
"defaultLayout": "4v",
"layouts": [
{
"id": "4v",
"label": "4 Shots Vertical",
"preview": "preview_4v.jpg",
"canvas": {
"w": 1500,
"h": 4000,
"bg": "#000000"
},
"overlay_path": "overlay.png",
"slots": [
{
"x": 110,
"y": 120,
"w": 1280,
"h": 720,
"capture": "instant",
"type": "image"
},
{
"x": 110,
"y": 940,
"w": 1280,
"h": 720,
"capture": "timed",
"timedDuration": 3,
"type": "video"
}
],
"widgets": [
{
"id": "date_display",
"type": "text",
"content": "{CURRENT_DATE}",
"x": 990,
"y": 3800,
"fontSize": 64,
"color": "#FFFFFF",
"fontFamily": "Arial"
},
{
"id": "session_qr",
"type": "image",
"content": "{QR_CODE}",
"x": 110,
"y": 3700,
"w": 200,
"h": 200
}
]
}
]
}
overlay_path 填寫相對於 manifest 資料夾的路徑,loadModuleManifest() 會自動轉換為絕對路徑。
雲端取圖設定(選配)
雲端功能需設定以下元件:Cloudflare R2(媒體儲存)、Supabase(資料庫 + 定時清理)、Vercel(下載頁面)。
Cloudflare R2
媒體儲存(照片與影片)統一上傳至 Cloudflare R2,相較 Supabase Storage 具備更好的頻寬成本與影片大檔案支援。
- 在 Cloudflare 建立 R2 Bucket(例如命名為
cyber-booth-media)。 - 建立具有
Object Read/Object Write權限的 API Token(Access Key ID + Secret Access Key)。 - 開啟 Bucket 的公開存取(Custom Domain 或
r2.dev子網域),取得公開基底 URL。 - 在
.env中填入:
R2_ACCOUNT_ID = your_cloudflare_account_id
R2_ACCESS_KEY_ID = your_access_key_id
R2_SECRET_ACCESS_KEY = your_secret_access_key
R2_BUCKET_NAME = cyber-booth-media
PUBLIC_R2_CDN_URL = https://your-public-domain.example.com
uploader.js 使用 AWS SDK v3(@aws-sdk/client-s3)對接 R2 的 S3 相容 API,endpoint 設為 https://<ACCOUNT_ID>.r2.cloudflarestorage.com。
Supabase 資料庫
Supabase 保留資料庫功能,不再儲存媒體檔案。
- 建立資料表
collages,欄位:id uuid、session_id text、created_at timestamptz(自動索引)。
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
Vercel 下載頁面
- 修改
public-viewer/index.html中的公開媒體基底 URL 為 R2 的公開網域。 - 將
public-viewer/部署至 Vercel。 - 修改
uploader.js中的VERCEL_DOMAIN為部署後的公開網址。 - 下載頁面透過 Blob 下載機制強制觸發檔案儲存對話框,支援 Web Share API 直接分享至通訊軟體。
Session 安全
每次重置產生唯一 Session ID:ssn_{Date.now()}_{8位亂數},防止使用者猜測他人 QR code 連結。
未設定 SUPABASE_URL 或 R2 相關環境變數時,系統自動以本機模式運作,所有輸出僅存於 sessions/ 資料夾,不影響拍攝功能。
環境變數總覽
| 變數 | 必填 | 說明 |
|---|---|---|
SUPABASE_URL |
選填 | Supabase 專案 URL,未設定則本機模式 |
SUPABASE_SERVICE_ROLE_KEY |
選填 | Supabase Service Role Key |
R2_ACCOUNT_ID |
選填 | Cloudflare 帳號 ID |
R2_ACCESS_KEY_ID |
選填 | R2 API Token Access Key |
R2_SECRET_ACCESS_KEY |
選填 | R2 API Token Secret |
R2_BUCKET_NAME |
選填 | R2 Bucket 名稱 |
PUBLIC_R2_CDN_URL |
選填 | R2 公開媒體基底 URL |
技術棧
- Visuals: TouchDesigner, Python (TD Scripts)
- Backend: Node.js, Express, Socket.io
- Image Processing: Sharp
- Frontend: Vanilla JS, Tailwind CSS, QRious
- Storage: Cloudflare R2(照片 + 影片)
- Database & Functions: Supabase(DB, Edge Functions, pg_cron)
- Hosting: Vercel

