Skip to content

Commit 24875e0

Browse files
authored
Merge pull request #237 from Qixingchen/main
feat: add emotion, text-normalization and latex-read flags to speech synthesize
2 parents c98e9ce + b91c990 commit 24875e0

3 files changed

Lines changed: 78 additions & 1 deletion

File tree

src/commands/speech/synthesize.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ export default defineCommand({
2626
{ flag: '--speed <n>', description: 'Speech speed multiplier', type: 'number' },
2727
{ flag: '--volume <n>', description: 'Volume level', type: 'number' },
2828
{ flag: '--pitch <n>', description: 'Pitch adjustment', type: 'number' },
29+
{ flag: '--emotion <emotion>', description: 'Emotion: happy, sad, angry, fearful, disgusted, surprised, calm, fluent, whisper (passed through as-is; see API Reference below for new values)' },
30+
{ flag: '--text-normalization', description: 'Enable Chinese/English text normalization' },
31+
{ flag: '--latex-read', description: 'Read LaTeX formulas ($$...$$, Chinese only)' },
2932
{ flag: '--format <fmt>', description: `Audio format: ${formatList(T2A_FORMATS)} (default: mp3)` },
3033
{ flag: '--sample-rate <hz>', description: 'Sample rate (default: 32000)', type: 'number' },
3134
{ flag: '--bitrate <bps>', description: 'Bitrate (default: 128000)', type: 'number' },
@@ -77,6 +80,9 @@ export default defineCommand({
7780
speed: (flags.speed as number) ?? undefined,
7881
vol: (flags.volume as number) ?? undefined,
7982
pitch: (flags.pitch as number) ?? undefined,
83+
emotion: (flags.emotion as string) || undefined,
84+
text_normalization: flags.textNormalization === true ? true : undefined,
85+
latex_read: flags.latexRead === true ? true : undefined,
8086
},
8187
audio_setting: {
8288
format: (flags.format as string) || 'mp3',

src/types/api.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ export interface SpeechRequest {
9797
speed?: number;
9898
vol?: number;
9999
pitch?: number;
100+
// Emotion hint (e.g. 'happy', 'sad', 'calm'); passed through as-is — the
101+
// API validates the value, so no local enum to keep in sync.
102+
emotion?: string;
103+
text_normalization?: boolean;
104+
latex_read?: boolean;
100105
};
101106
audio_setting?: {
102107
format?: string;

test/commands/speech/synthesize.test.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,4 +397,70 @@ describe('speech synthesize format validation', () => {
397397
console.log = originalLog;
398398
}
399399
});
400-
});
400+
});
401+
402+
describe('speech synthesize voice_setting flags', () => {
403+
const config = {
404+
apiKey: 'test-key',
405+
region: 'global' as const,
406+
baseUrl: 'https://api.mmx.io',
407+
output: 'json' as const,
408+
timeout: 10,
409+
verbose: false,
410+
quiet: false,
411+
noColor: true,
412+
yes: false,
413+
dryRun: true,
414+
nonInteractive: true,
415+
async: false,
416+
};
417+
418+
const flags = {
419+
text: 'Hello',
420+
quiet: false,
421+
verbose: false,
422+
noColor: true,
423+
yes: false,
424+
dryRun: true,
425+
help: false,
426+
nonInteractive: true,
427+
async: false,
428+
};
429+
430+
async function dryRunRequest(extraFlags: Record<string, unknown>) {
431+
const originalLog = console.log;
432+
let output = '';
433+
console.log = (msg: string) => { output += msg; };
434+
try {
435+
await synthesizeCommand.execute(config, { ...flags, ...extraFlags });
436+
return JSON.parse(output).request;
437+
} finally {
438+
console.log = originalLog;
439+
}
440+
}
441+
442+
it('--emotion sets voice_setting.emotion', async () => {
443+
const request = await dryRunRequest({ emotion: 'happy' });
444+
expect(request.voice_setting.emotion).toBe('happy');
445+
});
446+
447+
it('--emotion passes through arbitrary values without local validation', async () => {
448+
const request = await dryRunRequest({ emotion: 'bored' });
449+
expect(request.voice_setting.emotion).toBe('bored');
450+
});
451+
452+
it('omits voice_setting.emotion when --emotion is not provided', async () => {
453+
const request = await dryRunRequest({});
454+
expect(request.voice_setting.emotion).toBeUndefined();
455+
});
456+
457+
it('--text-normalization sets voice_setting.text_normalization', async () => {
458+
const request = await dryRunRequest({ textNormalization: true });
459+
expect(request.voice_setting.text_normalization).toBe(true);
460+
});
461+
462+
it('--latex-read sets voice_setting.latex_read', async () => {
463+
const request = await dryRunRequest({ latexRead: true });
464+
expect(request.voice_setting.latex_read).toBe(true);
465+
});
466+
});

0 commit comments

Comments
 (0)