Skip to content

Commit 2d83191

Browse files
Merge pull request #73 from raylanlin/pr/music
feat(music): add --model flag, --output-format url, apiDocs
2 parents f9a3ecb + 045c016 commit 2d83191

2 files changed

Lines changed: 63 additions & 12 deletions

File tree

src/commands/music/cover.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ import { musicCoverModel } from './models';
1313

1414
export default defineCommand({
1515
name: 'music cover',
16-
description: 'Generate a cover version of a song based on reference audio (music-cover-free)',
16+
description: 'Generate a cover version of a song based on reference audio (music-cover / music-cover-free)',
17+
apiDocs: '/docs/api-reference/music-generation',
1718
usage: 'mmx music cover --prompt <text> (--audio <url> | --audio-file <path>) [--lyrics <text>] [--out <path>] [flags]',
1819
options: [
20+
{ flag: '--model <model>', description: 'Model: music-cover (Token Plan), music-cover-free (Pay-as-you-go, default). Override only if needed.' },
1921
{ flag: '--prompt <text>', description: 'Target cover style, e.g. "Indie folk, acoustic guitar, warm male vocal"' },
2022
{ flag: '--audio <url>', description: 'URL of the reference audio (mp3, wav, flac, etc. — 6s to 6min, max 50MB)' },
2123
{ flag: '--audio-file <path>', description: 'Local reference audio file (auto base64-encoded)' },
@@ -66,7 +68,15 @@ export default defineCommand({
6668
const outPath = (flags.out as string | undefined) ?? `cover_${ts}.${ext}`;
6769
const format = detectOutputFormat(config.output);
6870

69-
const model = musicCoverModel(config);
71+
const model = (flags.model as string) || musicCoverModel(config);
72+
const VALID_MODELS = ['music-cover', 'music-cover-free'];
73+
if (flags.model && !VALID_MODELS.includes(model)) {
74+
throw new CLIError(
75+
`Invalid model "${model}". Valid models: ${VALID_MODELS.join(', ')}`,
76+
ExitCode.USAGE,
77+
'mmx music cover --model music-cover',
78+
);
79+
}
7080
const body: MusicRequest = {
7181
model,
7282
prompt,

src/commands/music/generate.ts

Lines changed: 51 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,29 @@ import { musicGenerateModel } from './models';
1313

1414
export default defineCommand({
1515
name: 'music generate',
16-
description: 'Generate a song (music-2.6-free)',
16+
description: 'Generate a song (music-2.6 / music-2.6-free / music-2.5+ / music-2.5)',
17+
apiDocs: '/docs/api-reference/music-generation',
1718
usage: 'mmx music generate --prompt <text> (--lyrics <text> | --instrumental | --lyrics-optimizer) [--out <path>] [flags]',
1819
options: [
19-
{ flag: '--prompt <text>', description: 'Music style description (can be detailed — see examples)' },
20-
{ flag: '--lyrics <text>', description: 'Song lyrics with structure tags. Required unless --instrumental or --lyrics-optimizer is used.' },
21-
{ flag: '--lyrics-file <path>', description: 'Read lyrics from file (use - for stdin)' },
20+
{ flag: '--prompt <text>', description: 'Music style description (e.g. "cinematic orchestral, building tension"). Max 2000 chars when combined with structured flags.' },
21+
{ flag: '--lyrics <text>', description: 'Song lyrics with structure tags (newline separated). Supported: [Intro], [Verse], [Pre Chorus], [Chorus], [Interlude], [Bridge], [Outro], [Post Chorus], [Transition], [Break], [Hook], [Build Up], [Inst], [Solo]. Tags must be clean — no descriptions inside brackets (they will be sung). Max 3500 chars.' },
22+
{ flag: '--lyrics-file <path>', description: 'Read lyrics from file (use - for stdin). Same tag rules as --lyrics.' },
2223
{ flag: '--lyrics-optimizer', description: 'Auto-generate lyrics from prompt (cannot be used with --lyrics or --instrumental)' },
23-
{ flag: '--instrumental', description: 'Generate instrumental music (no vocals). Cannot be used with --lyrics.' },
24+
{ flag: '--instrumental', description: 'Generate instrumental music (no vocals). music-2.6/music-2.5+: native is_instrumental flag; music-2.5: lyrics workaround. Cannot be used with --lyrics.' },
2425
{ flag: '--vocals <text>', description: 'Vocal style, e.g. "warm male baritone", "bright female soprano", "duet with harmonies"' },
25-
{ flag: '--genre <text>', description: 'Music genre, e.g. folk, pop, jazz' },
26+
{ flag: '--genre <text>', description: 'Music genre, e.g. folk, pop, jazz, electronic' },
2627
{ flag: '--mood <text>', description: 'Mood or emotion, e.g. warm, melancholic, uplifting' },
27-
{ flag: '--instruments <text>', description: 'Instruments to feature, e.g. "acoustic guitar, piano"' },
28+
{ flag: '--instruments <text>', description: 'Instruments to feature, e.g. "acoustic guitar, piano, strings"' },
2829
{ flag: '--tempo <text>', description: 'Tempo description, e.g. fast, slow, moderate' },
2930
{ flag: '--bpm <number>', description: 'Exact tempo in beats per minute', type: 'number' },
3031
{ flag: '--key <text>', description: 'Musical key, e.g. C major, A minor, G sharp' },
3132
{ flag: '--avoid <text>', description: 'Elements to avoid in the generated music' },
3233
{ flag: '--use-case <text>', description: 'Use case context, e.g. "background music for video", "theme song"' },
3334
{ flag: '--structure <text>', description: 'Song structure, e.g. "verse-chorus-verse-bridge-chorus"' },
34-
{ flag: '--references <text>', description: 'Reference tracks or artists, e.g. "similar to Ed Sheeran, Taylor Swift"' },
35+
{ flag: '--references <text>', description: 'Reference tracks or artists, e.g. "similar to Ed Sheeran"' },
3536
{ flag: '--extra <text>', description: 'Additional fine-grained requirements not covered above' },
37+
{ flag: '--model <model>', description: 'Model: music-2.6 (recommended), music-2.6-free (default, unlimited), music-2.5+, or music-2.5.' },
38+
{ flag: '--output-format <fmt>', description: 'Return format: hex (default, saved to file) or url (24h expiry, download promptly). When --stream, only hex.' },
3639
{ flag: '--aigc-watermark', description: 'Embed AI-generated content watermark in audio for content provenance' },
3740
{ flag: '--format <fmt>', description: 'Audio format (default: mp3)' },
3841
{ flag: '--sample-rate <hz>', description: 'Sample rate (default: 44100)', type: 'number' },
@@ -47,6 +50,10 @@ export default defineCommand({
4750
'mmx music generate --prompt "Upbeat pop about summer" --lyrics-optimizer --out summer.mp3',
4851
'# Instrumental:',
4952
'mmx music generate --prompt "Cinematic orchestral, building tension" --instrumental --out bgm.mp3',
53+
'# URL output (24h expiry — download promptly):',
54+
'mmx music generate --prompt "Upbeat pop" --lyrics "La la la..." --output-format url',
55+
'# Instrumental with music-2.5+:',
56+
'mmx music generate --prompt "Cinematic orchestral" --model "music-2.5+" --instrumental --out bgm.mp3',
5057
'# Detailed prompt with vocal characteristics:',
5158
'mmx music generate --prompt "Warm morning folk" --vocals "male and female duet, harmonies in chorus" --instruments "acoustic guitar, piano" --bpm 95 --lyrics-file song.txt --out duet.mp3',
5259
],
@@ -117,7 +124,30 @@ export default defineCommand({
117124
const outPath = (flags.out as string | undefined) ?? `music_${ts}.${ext}`;
118125
const format = detectOutputFormat(config.output);
119126

120-
const model = musicGenerateModel(config);
127+
const model = (flags.model as string) || musicGenerateModel(config);
128+
const VALID_MODELS = ['music-2.6', 'music-2.6-free', 'music-2.5+', 'music-2.5'];
129+
if (flags.model && !VALID_MODELS.includes(model)) {
130+
throw new CLIError(
131+
`Invalid model "${model}". Valid models: ${VALID_MODELS.join(', ')}`,
132+
ExitCode.USAGE,
133+
'mmx music generate --model music-2.6',
134+
);
135+
}
136+
const outFormat = (flags.outputFormat as string) || 'hex';
137+
if (outFormat !== 'hex' && outFormat !== 'url') {
138+
throw new CLIError(
139+
'--output-format must be "hex" or "url".',
140+
ExitCode.USAGE,
141+
'mmx music generate --output-format url',
142+
);
143+
}
144+
if (flags.stream && outFormat === 'url') {
145+
throw new CLIError(
146+
'--stream and --output-format url cannot be used together. Streaming requires hex format.',
147+
ExitCode.USAGE,
148+
'mmx music generate --output-format url',
149+
);
150+
}
121151
const body: MusicRequest = {
122152
model,
123153
prompt,
@@ -129,7 +159,7 @@ export default defineCommand({
129159
sample_rate: (flags.sampleRate as number) ?? 44100,
130160
bitrate: (flags.bitrate as number) ?? 256000,
131161
},
132-
output_format: 'hex',
162+
output_format: (flags.stream === true ? 'hex' : outFormat) as 'hex' | 'url',
133163
stream: flags.stream === true,
134164
};
135165

@@ -162,6 +192,17 @@ export default defineCommand({
162192
});
163193

164194
if (!config.quiet) process.stderr.write(`[Model: ${model}]\n`);
195+
if (outFormat === 'url') {
196+
if (response.data?.audio_url) {
197+
console.log(response.data.audio_url);
198+
} else {
199+
throw new CLIError(
200+
'Requested URL output but API did not return audio_url.',
201+
ExitCode.GENERAL,
202+
);
203+
}
204+
return;
205+
}
165206
saveAudioOutput(response, outPath, format, config.quiet);
166207
},
167208
});

0 commit comments

Comments
 (0)