Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/commands/music/cover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ import { musicCoverModel } from './models';

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

const model = musicCoverModel(config);
const model = (flags.model as string) || musicCoverModel(config);
const VALID_MODELS = ['music-cover', 'music-cover-free'];
if (flags.model && !VALID_MODELS.includes(model)) {
throw new CLIError(
`Invalid model "${model}". Valid models: ${VALID_MODELS.join(', ')}`,
ExitCode.USAGE,
'mmx music cover --model music-cover',
);
}
const body: MusicRequest = {
model,
prompt,
Expand Down
61 changes: 51 additions & 10 deletions src/commands/music/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,29 @@ import { musicGenerateModel } from './models';

export default defineCommand({
name: 'music generate',
description: 'Generate a song (music-2.6-free)',
description: 'Generate a song (music-2.6 / music-2.6-free / music-2.5+ / music-2.5)',
apiDocs: '/docs/api-reference/music-generation',
usage: 'mmx music generate --prompt <text> (--lyrics <text> | --instrumental | --lyrics-optimizer) [--out <path>] [flags]',
options: [
{ flag: '--prompt <text>', description: 'Music style description (can be detailed — see examples)' },
{ flag: '--lyrics <text>', description: 'Song lyrics with structure tags. Required unless --instrumental or --lyrics-optimizer is used.' },
{ flag: '--lyrics-file <path>', description: 'Read lyrics from file (use - for stdin)' },
{ flag: '--prompt <text>', description: 'Music style description (e.g. "cinematic orchestral, building tension"). Max 2000 chars when combined with structured flags.' },
{ 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.' },
{ flag: '--lyrics-file <path>', description: 'Read lyrics from file (use - for stdin). Same tag rules as --lyrics.' },
{ flag: '--lyrics-optimizer', description: 'Auto-generate lyrics from prompt (cannot be used with --lyrics or --instrumental)' },
{ flag: '--instrumental', description: 'Generate instrumental music (no vocals). Cannot be used with --lyrics.' },
{ 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.' },
{ flag: '--vocals <text>', description: 'Vocal style, e.g. "warm male baritone", "bright female soprano", "duet with harmonies"' },
{ flag: '--genre <text>', description: 'Music genre, e.g. folk, pop, jazz' },
{ flag: '--genre <text>', description: 'Music genre, e.g. folk, pop, jazz, electronic' },
{ flag: '--mood <text>', description: 'Mood or emotion, e.g. warm, melancholic, uplifting' },
{ flag: '--instruments <text>', description: 'Instruments to feature, e.g. "acoustic guitar, piano"' },
{ flag: '--instruments <text>', description: 'Instruments to feature, e.g. "acoustic guitar, piano, strings"' },
{ flag: '--tempo <text>', description: 'Tempo description, e.g. fast, slow, moderate' },
{ flag: '--bpm <number>', description: 'Exact tempo in beats per minute', type: 'number' },
{ flag: '--key <text>', description: 'Musical key, e.g. C major, A minor, G sharp' },
{ flag: '--avoid <text>', description: 'Elements to avoid in the generated music' },
{ flag: '--use-case <text>', description: 'Use case context, e.g. "background music for video", "theme song"' },
{ flag: '--structure <text>', description: 'Song structure, e.g. "verse-chorus-verse-bridge-chorus"' },
{ flag: '--references <text>', description: 'Reference tracks or artists, e.g. "similar to Ed Sheeran, Taylor Swift"' },
{ flag: '--references <text>', description: 'Reference tracks or artists, e.g. "similar to Ed Sheeran"' },
{ flag: '--extra <text>', description: 'Additional fine-grained requirements not covered above' },
{ flag: '--model <model>', description: 'Model: music-2.6 (recommended), music-2.6-free (default, unlimited), music-2.5+, or music-2.5.' },
{ flag: '--output-format <fmt>', description: 'Return format: hex (default, saved to file) or url (24h expiry, download promptly). When --stream, only hex.' },
{ flag: '--aigc-watermark', description: 'Embed AI-generated content watermark in audio for content provenance' },
{ flag: '--format <fmt>', description: 'Audio format (default: mp3)' },
{ flag: '--sample-rate <hz>', description: 'Sample rate (default: 44100)', type: 'number' },
Expand All @@ -47,6 +50,10 @@ export default defineCommand({
'mmx music generate --prompt "Upbeat pop about summer" --lyrics-optimizer --out summer.mp3',
'# Instrumental:',
'mmx music generate --prompt "Cinematic orchestral, building tension" --instrumental --out bgm.mp3',
'# URL output (24h expiry — download promptly):',
'mmx music generate --prompt "Upbeat pop" --lyrics "La la la..." --output-format url',
'# Instrumental with music-2.5+:',
'mmx music generate --prompt "Cinematic orchestral" --model "music-2.5+" --instrumental --out bgm.mp3',
'# Detailed prompt with vocal characteristics:',
'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',
],
Expand Down Expand Up @@ -117,7 +124,30 @@ export default defineCommand({
const outPath = (flags.out as string | undefined) ?? `music_${ts}.${ext}`;
const format = detectOutputFormat(config.output);

const model = musicGenerateModel(config);
const model = (flags.model as string) || musicGenerateModel(config);
const VALID_MODELS = ['music-2.6', 'music-2.6-free', 'music-2.5+', 'music-2.5'];
if (flags.model && !VALID_MODELS.includes(model)) {
throw new CLIError(
`Invalid model "${model}". Valid models: ${VALID_MODELS.join(', ')}`,
ExitCode.USAGE,
'mmx music generate --model music-2.6',
);
}
const outFormat = (flags.outputFormat as string) || 'hex';
if (outFormat !== 'hex' && outFormat !== 'url') {
throw new CLIError(
'--output-format must be "hex" or "url".',
ExitCode.USAGE,
'mmx music generate --output-format url',
);
}
if (flags.stream && outFormat === 'url') {
throw new CLIError(
'--stream and --output-format url cannot be used together. Streaming requires hex format.',
ExitCode.USAGE,
'mmx music generate --output-format url',
);
}
const body: MusicRequest = {
model,
prompt,
Expand All @@ -129,7 +159,7 @@ export default defineCommand({
sample_rate: (flags.sampleRate as number) ?? 44100,
bitrate: (flags.bitrate as number) ?? 256000,
},
output_format: 'hex',
output_format: (flags.stream === true ? 'hex' : outFormat) as 'hex' | 'url',
stream: flags.stream === true,
};

Expand Down Expand Up @@ -162,6 +192,17 @@ export default defineCommand({
});

if (!config.quiet) process.stderr.write(`[Model: ${model}]\n`);
if (outFormat === 'url') {
if (response.data?.audio_url) {
console.log(response.data.audio_url);
} else {
throw new CLIError(
'Requested URL output but API did not return audio_url.',
ExitCode.GENERAL,
);
}
return;
}
saveAudioOutput(response, outPath, format, config.quiet);
},
});
Loading