diff --git a/src/commands/music/cover.ts b/src/commands/music/cover.ts index 549feb8..54e2778 100644 --- a/src/commands/music/cover.ts +++ b/src/commands/music/cover.ts @@ -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 (--audio | --audio-file ) [--lyrics ] [--out ] [flags]', options: [ + { flag: '--model ', description: 'Model: music-cover (Token Plan), music-cover-free (Pay-as-you-go, default). Override only if needed.' }, { flag: '--prompt ', description: 'Target cover style, e.g. "Indie folk, acoustic guitar, warm male vocal"' }, { flag: '--audio ', description: 'URL of the reference audio (mp3, wav, flac, etc. — 6s to 6min, max 50MB)' }, { flag: '--audio-file ', description: 'Local reference audio file (auto base64-encoded)' }, @@ -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, diff --git a/src/commands/music/generate.ts b/src/commands/music/generate.ts index 6564883..3a0c046 100644 --- a/src/commands/music/generate.ts +++ b/src/commands/music/generate.ts @@ -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 (--lyrics | --instrumental | --lyrics-optimizer) [--out ] [flags]', options: [ - { flag: '--prompt ', description: 'Music style description (can be detailed — see examples)' }, - { flag: '--lyrics ', description: 'Song lyrics with structure tags. Required unless --instrumental or --lyrics-optimizer is used.' }, - { flag: '--lyrics-file ', description: 'Read lyrics from file (use - for stdin)' }, + { flag: '--prompt ', description: 'Music style description (e.g. "cinematic orchestral, building tension"). Max 2000 chars when combined with structured flags.' }, + { flag: '--lyrics ', 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 ', 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 ', description: 'Vocal style, e.g. "warm male baritone", "bright female soprano", "duet with harmonies"' }, - { flag: '--genre ', description: 'Music genre, e.g. folk, pop, jazz' }, + { flag: '--genre ', description: 'Music genre, e.g. folk, pop, jazz, electronic' }, { flag: '--mood ', description: 'Mood or emotion, e.g. warm, melancholic, uplifting' }, - { flag: '--instruments ', description: 'Instruments to feature, e.g. "acoustic guitar, piano"' }, + { flag: '--instruments ', description: 'Instruments to feature, e.g. "acoustic guitar, piano, strings"' }, { flag: '--tempo ', description: 'Tempo description, e.g. fast, slow, moderate' }, { flag: '--bpm ', description: 'Exact tempo in beats per minute', type: 'number' }, { flag: '--key ', description: 'Musical key, e.g. C major, A minor, G sharp' }, { flag: '--avoid ', description: 'Elements to avoid in the generated music' }, { flag: '--use-case ', description: 'Use case context, e.g. "background music for video", "theme song"' }, { flag: '--structure ', description: 'Song structure, e.g. "verse-chorus-verse-bridge-chorus"' }, - { flag: '--references ', description: 'Reference tracks or artists, e.g. "similar to Ed Sheeran, Taylor Swift"' }, + { flag: '--references ', description: 'Reference tracks or artists, e.g. "similar to Ed Sheeran"' }, { flag: '--extra ', description: 'Additional fine-grained requirements not covered above' }, + { flag: '--model ', description: 'Model: music-2.6 (recommended), music-2.6-free (default, unlimited), music-2.5+, or music-2.5.' }, + { flag: '--output-format ', 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 ', description: 'Audio format (default: mp3)' }, { flag: '--sample-rate ', description: 'Sample rate (default: 44100)', type: 'number' }, @@ -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', ], @@ -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, @@ -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, }; @@ -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); }, });