Documentation Index
Fetch the complete documentation index at: https://tech.illasoft.com/llms.txt
Use this file to discover all available pages before exploring further.
kira-music-worker 是音乐生成 Inngest function consumer。从 kira-be 接收事件,调用 KIE AI (Suno unofficial) 生成音乐,做后处理(封面生成、歌词对齐、ID3 嵌入),上传到 Supabase Storage,推送 Centrifugo 通知。
架构重要变化:2026-04-02 从 NATS 消费者重构为 Inngest HTTP function(commit 0f2da43)。MiniMax Music 2.5 和 Mureka API 已移除(commit afc6058),当前仅 KIE AI 三条线路。
部署形态
| 项 | 值 |
|---|
| 平台 | Fly.io |
| 对外端口 | 无(仅 kira-music-worker.internal) |
| 进程 | HTTP server,接收 Inngest push |
| 入口 | src/index.ts → serve({ client: inngest, functions }) |
Inngest Functions
4 个 function,分两类:
生成类(有并发限制)
| Function ID | Event | 并发 | Provider |
|---|
music-generation-kie-suno | music/generation-kie-suno.requested | per-user-per-tool=1,per-provider=100 | SunoUnofficialProvider |
music-instrumental-kie-suno | music/instrumental-kie-suno.requested | 同上 | SunoInstrumentalProvider |
music-add-vocals-kie-suno | music/add-vocals-kie-suno.requested | 同上 | SunoAddVocalsProvider |
非生成类(无限并发)
| Function ID | Event | 用途 |
|---|
music-upload-kira | music/upload-kira.requested | 用户直传音频 |
music-trim-kira | music/trim-kira.requested | 音频修剪 |
(还有一个 music-cancelled-cleanup 监听 inngest/function.cancelled 做退款/通知。)
标准处理 Pipeline(music generation)
源:src/processor.ts:81-154
step 1: check
├─ checkThreadExists(threadId) # thread 被删则跳过
├─ getMusicCreditsCost(toolName) = 25 # 固定 25 credits
├─ checkMusicEligibility(userId, 25) # 余额 / plan
├─ deductCredits(userId, 25, taskId) # 原子扣减(行锁)
└─ { skip, startTime }
step 2: notify-processing
├─ updateMusicInJsonb(taskId, { status: "processing" })
├─ updateMessageToolOutput(...)
└─ notifyUser(..., { type: "audio_status", audio: { status: "processing" } })
step 3: submit-task
├─ provider = getProviderByToolName(toolName)
└─ providerTaskId = await provider.submitTask(task)
step 4: poll-result
└─ pollWithInngestSteps(step, () => provider.pollTask(providerTaskId))
# 用 step.sleep + step.waitForEvent 做 durable polling
# 返回 GenerationResult { success, audioUrl, ... }
step 5: post-process
└─ processAudio(generationResult, task) # 见下方详解
step 6: finalize
├─ trackToolUsage({ userId, threadId, taskId, tool_name, success, duration_ms, credits_consumed })
├─ trackMediaUsage({ taskId, toolName, provider, model, duration_ms, output_duration_ms }) # 仅时长,无金额
└─ handleSuccess(taskId, userId, threadId, audioResult, task)
├─ checkAudioCsam({ id3CoverBuffer, lyrics, audioFileId, coverId, ... })
├─ updateMusicInJsonb(taskId, { status: "completed", audioId, coverId, ... })
└─ notifyUser(..., { type: "audio_status", audio: { status: "completed", url, coverUrl, ... } })
Post-Processing 五步(src/post-processor.ts)
1. download 下载 Suno 原始音频 URL
├─ fetch(audioUrl)
├─ 按 Content-Type 推断格式(mp3/wav/ogg/flac)
└─ Buffer
2. generate cover 生成封面图
├─ 优先:从 Suno generation result 提取 cover_url
└─ fallback:
├─ BytePlus SeedReam `seedream-5-0-260128` 模型
├─ prompt: "Album cover art for a song titled '{title}'. {prompt}. Professional..."
├─ sharp resize to 2048×2048 JPEG quality 90%
└─ generateBlurhash (4×4 components, 32×32 resize)
3. STT + forced alignment 歌词时间戳对齐
├─ KIE AI STT # 音频 → 文本
├─ ElevenLabs Scribe v2 # 字级时间戳
└─ Word[] = [{ word, start, end }, ...]
4. embed ID3 嵌入元数据
├─ 用 mp3tag.js (Bun shim)
└─ tags:
├─ TIT2 (title)
├─ TPE1 (artist)
├─ APIC (cover JPEG)
├─ SYLT (synced lyrics, word-level timestamps)
└─ USLT (unsynced lyrics, full text)
5. upload 上传到 Supabase Storage
├─ audio: {userId}/{threadId}/music_{audioId}.mp3 cacheControl:3600
└─ cover: {userId}/{threadId}/mc_{coverId}.jpg metadata: blurhash, size
Providers(当前)
仅 KIE AI 三路(MiniMax / Mureka 已删除):
| toolName | Provider 类 | Suno 模式 |
|---|
generateMusic | SunoUnofficialProvider | 常规生成(lyrics + style prompt) |
generateInstrumental | SunoInstrumentalProvider | 无人声纯乐器 |
addVocals | SunoAddVocalsProvider | 对已有音频添加/替换人声 |
addVocals 特有参数:vocalStyle (required)、styleWeight、weirdnessConstraint、audioWeight。
CSAM 审核(src/csam.ts)
在 finalize step 后触发 handleSuccess → checkAudioCsam:
并发:
├─ detectCsamFromBuffer(id3CoverBuffer) # 封面图
└─ detectCsamFromText(lyrics) # 歌词全文
命中任一 → block:
├─ 删除 audioFileId + coverId(若存在)
├─ 写 failed_uploads 表
├─ notify { type: "audio_status", audio: { status: "failed", errorCode: "csam_blocked" } }
└─ credits 不退款(合规处罚)
Centrifugo 推送
- Channel 格式:
{userId}/{threadId}#{userId}
- 端点:
{CENTRIFUGO_URL}/api/publish,X-API-Key 认证
{
"type": "audio_status",
"taskId": "...",
"audio": {
"taskId": "...",
"status": "processing" | "completed" | "failed" | "insufficient_credits" | "insufficient_plan",
"url": "https://...", // completed 时
"coverUrl": "https://...", // completed 时
"duration": 30.5,
"blurhash": "U6PVzY...",
"errorCode": "csam_blocked" | ...,
"errorMessage": "..."
},
"timestamp": "2026-04-14T12:34:56Z"
}
Cleanup 机制
music-cancelled-cleanup function 监听 inngest/function.cancelled:
- 非 upload/trim 类:退款 credits + 通知用户
failed / cancelled
- upload/trim 类:credits 本就为 0,只通知
手动取消路径:
- kira-be 发
music/task.cancelled event
- kira-be 设 Redis
cancel:{taskId} flag (TTL 3600s)
- Worker 每个 step 开头检查 flag,命中即抛
NonRetriableError
Billing
| 工具 | Credits | 备注 |
|---|
generateMusic | 25 | 固定 |
generateInstrumental | 25 | 固定 |
addVocals | 25 | 固定 |
upload | 0 | 免费 |
trimAudio | 0 | 免费 |
CSAM 阻断 不退款;insufficient_credits / insufficient_plan 不扣费。
环境变量
SUPABASE_URL
SUPABASE_KEY
REDIS_URL # Dragonfly,cancel flag
INNGEST_EVENT_KEY
INNGEST_SIGNING_KEY
KIE_AI_API_KEY # Suno unofficial
BYTEPLUS_API_KEY # SeedReam 封面
ELEVENLABS_API_KEY # Scribe v2 forced alignment
CENTRIFUGO_URL
CENTRIFUGO_API_KEY
POSTHOG_API_KEY
SENTRY_DSN
相关文档