dsh-audiogen 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +248 -229
- package/lib/client.js.map +1 -1
- package/lib/index.js +38 -4
- package/package.json +1 -1
- package/skills/music/SKILL.md +30 -7
- package/src/agent-audio-tools.ts +5 -1
- package/src/audio-engine.ts +20 -4
- package/src/client/AudioGenPanel.tsx +23 -2
- package/src/protocol.ts +5 -1
- package/src/routes.ts +2 -0
package/lib/index.js
CHANGED
|
@@ -116,6 +116,7 @@ function findBase64Audio(value) {
|
|
|
116
116
|
const record = value;
|
|
117
117
|
for (const key of [
|
|
118
118
|
"audio",
|
|
119
|
+
"music",
|
|
119
120
|
"b64_json",
|
|
120
121
|
"base64",
|
|
121
122
|
"data",
|
|
@@ -423,15 +424,36 @@ async function minimax(channel, request, signal) {
|
|
|
423
424
|
}];
|
|
424
425
|
}
|
|
425
426
|
if (request.mode === "music") {
|
|
427
|
+
const MUSIC_FORMATS = /* @__PURE__ */ new Set([
|
|
428
|
+
"mp3",
|
|
429
|
+
"wav",
|
|
430
|
+
"pcm"
|
|
431
|
+
]);
|
|
432
|
+
const MUSIC_SAMPLE_RATES = /* @__PURE__ */ new Set([
|
|
433
|
+
16e3,
|
|
434
|
+
24e3,
|
|
435
|
+
32e3,
|
|
436
|
+
44100
|
|
437
|
+
]);
|
|
438
|
+
const MUSIC_BITRATES = /* @__PURE__ */ new Set([
|
|
439
|
+
32e3,
|
|
440
|
+
64e3,
|
|
441
|
+
128e3,
|
|
442
|
+
256e3
|
|
443
|
+
]);
|
|
444
|
+
const lyrics = request.lyrics?.trim() ?? "";
|
|
445
|
+
if (lyrics === "" && request.isInstrumental !== true) throw new AudioGenError("MiniMax 音乐生成需要歌词(lyrics 参数),或在「纯音乐」模式(is_instrumental=true)下生成;也可让面板/Agent 先为提示词创作一段歌词。", "lyrics-required");
|
|
426
446
|
const endpoint = `${base}/music_generation`;
|
|
427
447
|
const body = {
|
|
428
448
|
model,
|
|
429
449
|
prompt: request.prompt,
|
|
450
|
+
...lyrics === "" ? {} : { lyrics },
|
|
451
|
+
...request.isInstrumental !== void 0 ? { is_instrumental: request.isInstrumental } : {},
|
|
430
452
|
...request.duration !== void 0 ? { duration: request.duration } : {},
|
|
431
453
|
audio_setting: {
|
|
432
|
-
format: request.format ?? "mp3",
|
|
433
|
-
sample_rate: 44100,
|
|
434
|
-
bitrate: 256e3
|
|
454
|
+
format: MUSIC_FORMATS.has(request.format ?? "mp3") ? request.format ?? "mp3" : "mp3",
|
|
455
|
+
sample_rate: MUSIC_SAMPLE_RATES.has(request.sampleRate ?? 44100) ? request.sampleRate ?? 44100 : 44100,
|
|
456
|
+
bitrate: MUSIC_BITRATES.has(request.bitrate ?? 256e3) ? request.bitrate ?? 256e3 : 256e3
|
|
435
457
|
}
|
|
436
458
|
};
|
|
437
459
|
const response = await fetchWithTimeout(endpoint, {
|
|
@@ -1054,6 +1076,8 @@ function parseGenerateRequest(body) {
|
|
|
1054
1076
|
...typeof body.previewText === "string" && body.previewText.trim() !== "" ? { previewText: body.previewText.trim() } : {},
|
|
1055
1077
|
...num(body.speed) !== void 0 ? { speed: num(body.speed) } : {},
|
|
1056
1078
|
...num(body.duration) !== void 0 ? { duration: num(body.duration) } : {},
|
|
1079
|
+
...typeof body.lyrics === "string" && body.lyrics.trim() !== "" ? { lyrics: body.lyrics.trim() } : {},
|
|
1080
|
+
...typeof body.isInstrumental === "boolean" ? { isInstrumental: body.isInstrumental } : {},
|
|
1057
1081
|
...typeof body.format === "string" && body.format.trim() !== "" ? { format: body.format.trim() } : {},
|
|
1058
1082
|
...typeof body.channelId === "string" && body.channelId !== "" ? { channelId: body.channelId } : {},
|
|
1059
1083
|
...str(body.emotion) !== void 0 ? { emotion: str(body.emotion) } : {},
|
|
@@ -1592,9 +1616,17 @@ function registerAgentAudioTools(ctx, resolve) {
|
|
|
1592
1616
|
type: "number",
|
|
1593
1617
|
description: "Requested duration in seconds for music/sfx."
|
|
1594
1618
|
},
|
|
1619
|
+
lyrics: {
|
|
1620
|
+
type: "string",
|
|
1621
|
+
description: "Lyrics for music generation (MiniMax music-3.0/music-cover). Required unless is_instrumental is true. Split verses with an empty line."
|
|
1622
|
+
},
|
|
1623
|
+
is_instrumental: {
|
|
1624
|
+
type: "boolean",
|
|
1625
|
+
description: "Generate purely instrumental music without vocals/lyrics (MiniMax is_instrumental). When true, lyrics may be omitted."
|
|
1626
|
+
},
|
|
1595
1627
|
format: {
|
|
1596
1628
|
type: "string",
|
|
1597
|
-
description: "Output format such as mp3 or wav."
|
|
1629
|
+
description: "Output format such as mp3 or wav. MiniMax music supports mp3/wav/pcm."
|
|
1598
1630
|
},
|
|
1599
1631
|
emotion: {
|
|
1600
1632
|
type: "string",
|
|
@@ -1730,6 +1762,8 @@ function registerAgentAudioTools(ctx, resolve) {
|
|
|
1730
1762
|
...typeof args.preview_text === "string" && args.preview_text.trim() !== "" ? { previewText: args.preview_text.trim() } : {},
|
|
1731
1763
|
...typeof args.speed === "number" ? { speed: args.speed } : {},
|
|
1732
1764
|
...typeof args.duration === "number" ? { duration: args.duration } : {},
|
|
1765
|
+
...typeof args.lyrics === "string" && args.lyrics.trim() !== "" ? { lyrics: args.lyrics.trim() } : {},
|
|
1766
|
+
...typeof args.is_instrumental === "boolean" ? { isInstrumental: args.is_instrumental } : {},
|
|
1733
1767
|
...typeof args.format === "string" && args.format.trim() !== "" ? { format: args.format.trim() } : {},
|
|
1734
1768
|
...typeof args.emotion === "string" && args.emotion.trim() !== "" ? { emotion: args.emotion.trim() } : {},
|
|
1735
1769
|
...typeof args.vol === "number" && Number.isFinite(args.vol) ? { vol: args.vol } : {},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-audiogen",
|
|
3
3
|
"description": "AI audio generation plugin for the dsh web GUI: multi-vendor TTS/music/sound-effect channels (OpenAI-compatible, ElevenLabs, MiniMax, Stability AI and custom), per-channel model/voice catalogs, Agent tool and a sidebar AI 音频 panel.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
package/skills/music/SKILL.md
CHANGED
|
@@ -2,15 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
## 触发
|
|
4
4
|
- `/audio:music <描述>`
|
|
5
|
-
- 用户说“生成一段音乐 / 配乐 / BGM
|
|
5
|
+
- 用户说“生成一段音乐 / 配乐 / BGM / 纯音乐”
|
|
6
6
|
|
|
7
7
|
## 参数
|
|
8
8
|
- prompt: 必填,风格/情绪/乐器/时长描述
|
|
9
|
-
- model:
|
|
10
|
-
-
|
|
11
|
-
-
|
|
9
|
+
- model: 可选,已配置的音频模型(MiniMax:music-3.0 / music-2.6 / music-cover)
|
|
10
|
+
- lyrics: 可选,歌词;**MiniMax music-3.0 / music-cover 必填**(除非 is_instrumental=true);多段用空行分隔
|
|
11
|
+
- is_instrumental: 可选,是否纯音乐(无歌词/人声),true 时 lyrics 可留空
|
|
12
|
+
- duration: 可选,秒数(MiniMax 一般 5-120s)
|
|
13
|
+
- format: 可选,MiniMax 音乐仅 mp3 / wav / pcm
|
|
12
14
|
|
|
13
15
|
## 流程
|
|
14
|
-
1. 确认已配置支持音乐生成的渠道(如 Stability Audio / 自定义)。
|
|
15
|
-
2.
|
|
16
|
-
3.
|
|
16
|
+
1. 确认已配置支持音乐生成的渠道(如 MiniMax / Stability Audio / 自定义)。
|
|
17
|
+
2. 若用户未给歌词且未要求纯音乐:对 MiniMax 渠道先创作/补全一段歌词再调用。
|
|
18
|
+
3. 调用 `generate_audio`,mode=music。
|
|
19
|
+
4. 将生成的音频 URL 返回给用户。
|
|
20
|
+
|
|
21
|
+
## MiniMax 官方 music_generation 字段参考(POST /v1/music_generation)
|
|
22
|
+
|
|
23
|
+
| 字段 | 工具参数 | 说明 |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| model | model | music-3.0 / music-2.6 / music-cover |
|
|
26
|
+
| prompt | prompt | 音乐风格/情绪/乐器描述(≤3000 字) |
|
|
27
|
+
| lyrics | lyrics | 歌词;多段用空行分隔;纯音乐模式可留空 |
|
|
28
|
+
| is_instrumental | is_instrumental | 默认 false;true = 纯音乐(无歌词/人声),此时 lyrics 可省 |
|
|
29
|
+
| duration | duration | 生成时长(秒) |
|
|
30
|
+
| audio_setting.format | format | **mp3 / wav / pcm**(仅此三种) |
|
|
31
|
+
| audio_setting.sample_rate | sample_rate | **16000 / 24000 / 32000 / 44100**(无 48000) |
|
|
32
|
+
| audio_setting.bitrate | bitrate | **32000 / 64000 / 128000 / 256000** |
|
|
33
|
+
|
|
34
|
+
> 引擎对音乐 audio_setting 按上述枚举校验,超出枚举的取值自动回退默认(format=mp3、sample_rate=44100、bitrate=256000)。
|
|
35
|
+
> 若不加 lyrics 也未开启纯音乐,引擎会直接提示 `lyrics-required`(MiniMax 上游返回 2013 lyrics is required)。
|
|
36
|
+
|
|
37
|
+
## 常见错误
|
|
38
|
+
- `lyrics-required`:MiniMax 音乐生成需要歌词,或开启纯音乐。
|
|
39
|
+
- `HTTP 400` 且含 `2013`:上游参数不合法,检查 lyrics / audio_setting 枚举。
|
package/src/agent-audio-tools.ts
CHANGED
|
@@ -109,7 +109,9 @@ export function registerAgentAudioTools(ctx: Context, resolve: () => AgentAudioT
|
|
|
109
109
|
preview_text: { type: 'string', description: 'Optional preview text for voice_design.' },
|
|
110
110
|
speed: { type: 'number', description: 'Optional speaking rate / speed multiplier where supported. MiniMax range 0.5-2.0 (default 1).' },
|
|
111
111
|
duration: { type: 'number', description: 'Requested duration in seconds for music/sfx.' },
|
|
112
|
-
|
|
112
|
+
lyrics: { type: 'string', description: 'Lyrics for music generation (MiniMax music-3.0/music-cover). Required unless is_instrumental is true. Split verses with an empty line.' },
|
|
113
|
+
is_instrumental: { type: 'boolean', description: 'Generate purely instrumental music without vocals/lyrics (MiniMax is_instrumental). When true, lyrics may be omitted.' },
|
|
114
|
+
format: { type: 'string', description: 'Output format such as mp3 or wav. MiniMax music supports mp3/wav/pcm.' },
|
|
113
115
|
// ---- MiniMax TTS only (ignored by other providers) ----
|
|
114
116
|
emotion: { type: 'string', description: 'MiniMax TTS emotion, e.g. happy/sad/angry/nervous/fearful/bored (voice_setting.emotion).' },
|
|
115
117
|
vol: { type: 'number', description: 'MiniMax TTS volume 0-10, default 1 (voice_setting.vol).' },
|
|
@@ -195,6 +197,8 @@ export function registerAgentAudioTools(ctx: Context, resolve: () => AgentAudioT
|
|
|
195
197
|
...(typeof args.preview_text === 'string' && args.preview_text.trim() !== '' ? { previewText: args.preview_text.trim() } : {}),
|
|
196
198
|
...(typeof args.speed === 'number' ? { speed: args.speed } : {}),
|
|
197
199
|
...(typeof args.duration === 'number' ? { duration: args.duration } : {}),
|
|
200
|
+
...(typeof args.lyrics === 'string' && args.lyrics.trim() !== '' ? { lyrics: args.lyrics.trim() } : {}),
|
|
201
|
+
...(typeof args.is_instrumental === 'boolean' ? { isInstrumental: args.is_instrumental } : {}),
|
|
198
202
|
...(typeof args.format === 'string' && args.format.trim() !== '' ? { format: args.format.trim() } : {}),
|
|
199
203
|
// ---- MiniMax TTS 专属字段 ----
|
|
200
204
|
...(typeof args.emotion === 'string' && args.emotion.trim() !== '' ? { emotion: args.emotion.trim() } : {}),
|
package/src/audio-engine.ts
CHANGED
|
@@ -124,7 +124,7 @@ function findBase64Audio(value: unknown): string | undefined {
|
|
|
124
124
|
}
|
|
125
125
|
if (value === null || typeof value !== 'object') return undefined
|
|
126
126
|
const record = value as Record<string, unknown>
|
|
127
|
-
for (const key of ['audio', 'b64_json', 'base64', 'data', 'output', 'result', 'value']) {
|
|
127
|
+
for (const key of ['audio', 'music', 'b64_json', 'base64', 'data', 'output', 'result', 'value']) {
|
|
128
128
|
const candidate = record[key]
|
|
129
129
|
const found = findBase64Audio(candidate)
|
|
130
130
|
if (found !== undefined) return found
|
|
@@ -437,15 +437,31 @@ async function minimax(channel: AudioChannel, request: GenerateAudioRequest, sig
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
if (request.mode === 'music') {
|
|
440
|
+
// MiniMax 音乐生成官方字段:model/lyrics/prompt/is_instrumental/duration/
|
|
441
|
+
// audio_setting{format, sample_rate, bitrate}。音频输出配置为固定枚举:
|
|
442
|
+
// format mp3|wav|pcm;sample_rate 16000|24000|32000|44100;
|
|
443
|
+
// bitrate 32000|64000|128000|256000,超出枚举的值回退默认。
|
|
444
|
+
const MUSIC_FORMATS = new Set(['mp3', 'wav', 'pcm'])
|
|
445
|
+
const MUSIC_SAMPLE_RATES = new Set([16000, 24000, 32000, 44100])
|
|
446
|
+
const MUSIC_BITRATES = new Set([32000, 64000, 128000, 256000])
|
|
447
|
+
const lyrics = request.lyrics?.trim() ?? ''
|
|
448
|
+
if (lyrics === '' && request.isInstrumental !== true) {
|
|
449
|
+
throw new AudioGenError(
|
|
450
|
+
'MiniMax 音乐生成需要歌词(lyrics 参数),或在「纯音乐」模式(is_instrumental=true)下生成;也可让面板/Agent 先为提示词创作一段歌词。',
|
|
451
|
+
'lyrics-required',
|
|
452
|
+
)
|
|
453
|
+
}
|
|
440
454
|
const endpoint = `${base}/music_generation`
|
|
441
455
|
const body: Record<string, unknown> = {
|
|
442
456
|
model,
|
|
443
457
|
prompt: request.prompt,
|
|
458
|
+
...(lyrics === '' ? {} : { lyrics }),
|
|
459
|
+
...(request.isInstrumental !== undefined ? { is_instrumental: request.isInstrumental } : {}),
|
|
444
460
|
...(request.duration !== undefined ? { duration: request.duration } : {}),
|
|
445
461
|
audio_setting: {
|
|
446
|
-
format: request.format ?? 'mp3',
|
|
447
|
-
sample_rate: 44100,
|
|
448
|
-
bitrate: 256000,
|
|
462
|
+
format: MUSIC_FORMATS.has(request.format ?? 'mp3') ? (request.format ?? 'mp3') : 'mp3',
|
|
463
|
+
sample_rate: MUSIC_SAMPLE_RATES.has(request.sampleRate ?? 44100) ? (request.sampleRate ?? 44100) : 44100,
|
|
464
|
+
bitrate: MUSIC_BITRATES.has(request.bitrate ?? 256000) ? (request.bitrate ?? 256000) : 256000,
|
|
449
465
|
},
|
|
450
466
|
}
|
|
451
467
|
const response = await fetchWithTimeout(endpoint, {
|
|
@@ -60,6 +60,8 @@ export function AudioGenPanel(props: { api: AudiogenApi; scope: AudiogenScope })
|
|
|
60
60
|
const [voice, setVoice] = useState('')
|
|
61
61
|
const [speed, setSpeed] = useState('')
|
|
62
62
|
const [duration, setDuration] = useState('')
|
|
63
|
+
const [lyrics, setLyrics] = useState('')
|
|
64
|
+
const [instrumental, setInstrumental] = useState(false)
|
|
63
65
|
const [format, setFormat] = useState('mp3')
|
|
64
66
|
// MiniMax TTS 高级参数(其他厂商忽略)
|
|
65
67
|
const [emotion, setEmotion] = useState('')
|
|
@@ -109,6 +111,8 @@ export function AudioGenPanel(props: { api: AudiogenApi; scope: AudiogenScope })
|
|
|
109
111
|
...(voice.trim() !== '' ? { voice: voice.trim() } : {}),
|
|
110
112
|
...(speed.trim() !== '' ? { speed: Number(speed) } : {}),
|
|
111
113
|
...(duration.trim() !== '' ? { duration: Number(duration) } : {}),
|
|
114
|
+
...(lyrics.trim() !== '' ? { lyrics: lyrics.trim() } : {}),
|
|
115
|
+
...(instrumental ? { isInstrumental: true } : {}),
|
|
112
116
|
...(format.trim() !== '' ? { format: format.trim() } : {}),
|
|
113
117
|
...(emotion.trim() !== '' ? { emotion: emotion.trim() } : {}),
|
|
114
118
|
...(vol.trim() !== '' ? { vol: Number(vol) } : {}),
|
|
@@ -252,14 +256,31 @@ export function AudioGenPanel(props: { api: AudiogenApi; scope: AudiogenScope })
|
|
|
252
256
|
</label>
|
|
253
257
|
) : null}
|
|
254
258
|
|
|
259
|
+
{mode === 'music' ? (
|
|
260
|
+
<>
|
|
261
|
+
<label className={css.label}>
|
|
262
|
+
<span>歌词(纯音乐模式可留空;多段用空行分隔)</span>
|
|
263
|
+
<textarea className={css.textarea} value={lyrics} onChange={event => setLyrics(event.target.value)} placeholder={'第一段歌词…\n\n第二段歌词…'} />
|
|
264
|
+
</label>
|
|
265
|
+
<label className={css.checkbox}>
|
|
266
|
+
<input type="checkbox" checked={instrumental} onChange={event => setInstrumental(event.target.checked)} />
|
|
267
|
+
<span>纯音乐(无歌词/人声)is_instrumental</span>
|
|
268
|
+
</label>
|
|
269
|
+
</>
|
|
270
|
+
) : null}
|
|
271
|
+
|
|
255
272
|
{needModel ? (
|
|
256
273
|
<label className={css.label}>
|
|
257
274
|
<span>{tt('format.label')}</span>
|
|
258
275
|
<select className={css.select} value={format} onChange={event => setFormat(event.target.value)}>
|
|
259
276
|
<option value="mp3">mp3</option>
|
|
260
277
|
<option value="wav">wav</option>
|
|
261
|
-
|
|
262
|
-
|
|
278
|
+
{mode === 'tts' ? (
|
|
279
|
+
<>
|
|
280
|
+
<option value="flac">flac</option>
|
|
281
|
+
<option value="ogg">ogg</option>
|
|
282
|
+
</>
|
|
283
|
+
) : null}
|
|
263
284
|
<option value="pcm">pcm</option>
|
|
264
285
|
</select>
|
|
265
286
|
</label>
|
package/src/protocol.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
export const AUDIOGEN_SETTINGS_NAMESPACE = 'dsh-audiogen'
|
|
9
9
|
|
|
10
10
|
/** Published package version shared by the host updater and the client UI. */
|
|
11
|
-
export const PLUGIN_VERSION = '0.3.
|
|
11
|
+
export const PLUGIN_VERSION = '0.3.3'
|
|
12
12
|
|
|
13
13
|
/** Same-origin route family (loopback-only, mirroring dsh-imagegen). */
|
|
14
14
|
export const SETTINGS_API = {
|
|
@@ -114,6 +114,10 @@ export interface GenerateAudioRequest {
|
|
|
114
114
|
speed?: number
|
|
115
115
|
/** Requested duration in seconds (music/sfx). */
|
|
116
116
|
duration?: number
|
|
117
|
+
/** MiniMax 音乐生成歌词;music-3.0 / music-cover 在非纯音乐模式下必填。 */
|
|
118
|
+
lyrics?: string
|
|
119
|
+
/** MiniMax 是否生成纯音乐(无歌词/人声);true 时 lyrics 可为空。 */
|
|
120
|
+
isInstrumental?: boolean
|
|
117
121
|
/** Output format, e.g. mp3, wav, pcm. */
|
|
118
122
|
format?: string
|
|
119
123
|
/** Channel this request targets (host falls back to default). */
|
package/src/routes.ts
CHANGED
|
@@ -122,6 +122,8 @@ function parseGenerateRequest(body: Record<string, unknown>): GenerateAudioReque
|
|
|
122
122
|
...(typeof body.previewText === 'string' && body.previewText.trim() !== '' ? { previewText: body.previewText.trim() } : {}),
|
|
123
123
|
...(num(body.speed) !== undefined ? { speed: num(body.speed)! } : {}),
|
|
124
124
|
...(num(body.duration) !== undefined ? { duration: num(body.duration)! } : {}),
|
|
125
|
+
...(typeof body.lyrics === 'string' && body.lyrics.trim() !== '' ? { lyrics: body.lyrics.trim() } : {}),
|
|
126
|
+
...(typeof body.isInstrumental === 'boolean' ? { isInstrumental: body.isInstrumental } : {}),
|
|
125
127
|
...(typeof body.format === 'string' && body.format.trim() !== '' ? { format: body.format.trim() } : {}),
|
|
126
128
|
...(typeof body.channelId === 'string' && body.channelId !== '' ? { channelId: body.channelId } : {}),
|
|
127
129
|
// ---- MiniMax TTS 专属字段(其他厂商忽略) ----
|