Skip to content

Commit 7324d50

Browse files
authored
Merge pull request #160 from MiniMax-AI/fix/music-cover-preprocess
fix: preprocess music cover lyrics flow
2 parents 7818341 + 2d65deb commit 7324d50

4 files changed

Lines changed: 57 additions & 9 deletions

File tree

src/client/endpoints.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ export function musicEndpoint(baseUrl: string): string {
3030
return `${baseUrl}/v1/music_generation`;
3131
}
3232

33+
export function musicCoverPreprocessEndpoint(baseUrl: string): string {
34+
return `${baseUrl}/v1/music_cover_preprocess`;
35+
}
36+
3337
export function searchEndpoint(baseUrl: string): string {
3438
return `${baseUrl}/v1/coding_plan/search`;
3539
}

src/commands/music/cover.ts

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { defineCommand } from '../../command';
33
import { CLIError } from '../../errors/base';
44
import { ExitCode } from '../../errors/codes';
55
import { request, requestJson } from '../../client/http';
6-
import { musicEndpoint } from '../../client/endpoints';
6+
import { musicCoverPreprocessEndpoint, musicEndpoint } from '../../client/endpoints';
77
import { formatOutput, detectOutputFormat } from '../../output/formatter';
88
import { saveAudioOutput } from '../../output/audio';
99
import { MUSIC_FORMATS, formatList, validateAudioFormat } from '../../utils/audio-formats';
1010
import { pipeAudioStream } from '../../utils/audio-stream';
1111
import type { Config } from '../../config/schema';
1212
import type { GlobalFlags } from '../../types/flags';
13-
import type { MusicRequest, MusicResponse } from '../../types/api';
13+
import type { CoverPreprocessRequest, CoverPreprocessResponse, MusicRequest, MusicResponse } from '../../types/api';
1414
import { musicCoverModel } from './models';
1515

1616
export default defineCommand({
@@ -23,8 +23,8 @@ export default defineCommand({
2323
{ flag: '--prompt <text>', description: 'Target cover style, e.g. "Indie folk, acoustic guitar, warm male vocal"' },
2424
{ flag: '--audio <url>', description: 'URL of the reference audio (mp3, wav, flac, etc. — 6s to 6min, max 50MB)' },
2525
{ flag: '--audio-file <path>', description: 'Local reference audio file (auto base64-encoded)' },
26-
{ flag: '--lyrics <text>', description: 'Cover lyrics. If omitted, extracted from reference audio via ASR.' },
27-
{ flag: '--lyrics-file <path>', description: 'Read lyrics from file (use - for stdin)' },
26+
{ flag: '--lyrics <text>', description: 'Cover lyrics. If provided, CLI auto-runs cover preprocess first. If omitted, lyrics are extracted from reference audio via ASR.' },
27+
{ flag: '--lyrics-file <path>', description: 'Read lyrics from file (use - for stdin). Triggers cover preprocess automatically.' },
2828
{ flag: '--seed <number>', description: 'Random seed 0–1000000 for reproducible results', type: 'number' },
2929
{ flag: '--format <fmt>', description: `Audio format: ${formatList(MUSIC_FORMATS)} (default: mp3)` },
3030
{ flag: '--sample-rate <hz>', description: 'Sample rate: 16000, 24000, 32000, 44100 (default: 44100)', type: 'number' },
@@ -80,6 +80,10 @@ export default defineCommand({
8080
'mmx music cover --model music-cover',
8181
);
8282
}
83+
const audioSource: Pick<MusicRequest, 'audio_url' | 'audio_base64'> = audioUrl
84+
? { audio_url: audioUrl }
85+
: { audio_base64: readFileSync(audioFile!).toString('base64') };
86+
8387
const body: MusicRequest = {
8488
model,
8589
prompt,
@@ -93,21 +97,41 @@ export default defineCommand({
9397
},
9498
output_format: 'hex',
9599
stream: flags.stream === true,
100+
...audioSource,
96101
};
97102

98-
if (audioUrl) {
99-
body.audio_url = audioUrl;
100-
} else {
101-
body.audio_base64 = readFileSync(audioFile!).toString('base64');
103+
const needsPreprocess = Boolean(lyrics?.trim());
104+
const preprocessBody: CoverPreprocessRequest | undefined = needsPreprocess
105+
? {
106+
model: 'music-cover',
107+
...audioSource,
108+
}
109+
: undefined;
110+
111+
if (needsPreprocess) {
112+
delete body.audio_url;
113+
delete body.audio_base64;
114+
body.cover_feature_id = '__cover_feature_id_from_preprocess__';
102115
}
103116

104117
if (config.dryRun) {
105-
console.log(formatOutput({ request: body }, format));
118+
console.log(formatOutput(needsPreprocess ? { preprocess_request: preprocessBody, request: body } : { request: body }, format));
106119
return;
107120
}
108121

109122
const url = musicEndpoint(config.baseUrl);
110123

124+
if (needsPreprocess) {
125+
const preprocessUrl = musicCoverPreprocessEndpoint(config.baseUrl);
126+
const preprocessResponse = await requestJson<CoverPreprocessResponse>(config, {
127+
url: preprocessUrl,
128+
method: 'POST',
129+
body: preprocessBody,
130+
});
131+
132+
body.cover_feature_id = preprocessResponse.cover_feature_id;
133+
}
134+
111135
if (flags.stream) {
112136
const res = await request(config, { url, method: 'POST', body, stream: true });
113137
await pipeAudioStream(res);

src/types/api.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ export interface MusicRequest {
212212
lyrics_optimizer?: boolean;
213213
audio_url?: string;
214214
audio_base64?: string;
215+
cover_feature_id?: string;
215216
seed?: number;
216217
audio_setting?: {
217218
format?: string;
@@ -239,6 +240,21 @@ export interface MusicResponse {
239240
};
240241
}
241242

243+
export interface CoverPreprocessRequest {
244+
model: 'music-cover';
245+
audio_url?: string;
246+
audio_base64?: string;
247+
}
248+
249+
export interface CoverPreprocessResponse {
250+
cover_feature_id: string;
251+
formatted_lyrics?: string;
252+
structure_result?: string;
253+
audio_duration?: number;
254+
trace_id?: string;
255+
base_resp: BaseResp;
256+
}
257+
242258
// ---- Quota ----
243259

244260
export interface QuotaResponse {

test/commands/music/cover.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ describe('music cover command', () => {
105105

106106
console.log = origLog;
107107
const parsed = JSON.parse(captured);
108+
expect(parsed.preprocess_request.model).toBe('music-cover');
109+
expect(parsed.preprocess_request.audio_url).toBe('https://example.com/ref.mp3');
108110
expect(parsed.request.lyrics).toBe('New lyrics here');
111+
expect(parsed.request.cover_feature_id).toBe('__cover_feature_id_from_preprocess__');
112+
expect(parsed.request.audio_url).toBeUndefined();
109113
});
110114

111115
it('accepts optional --seed', async () => {

0 commit comments

Comments
 (0)