@@ -6,6 +6,7 @@ import { speechEndpoint } from '../../client/endpoints';
66import { parseSSE } from '../../client/stream' ;
77import { detectOutputFormat , formatOutput } from '../../output/formatter' ;
88import { saveAudioOutput } from '../../output/audio' ;
9+ import { writeFileSync } from 'fs' ;
910import { readTextFromPathOrStdin } from '../../utils/fs' ;
1011import type { Config } from '../../config/schema' ;
1112import type { GlobalFlags } from '../../types/flags' ;
@@ -37,6 +38,7 @@ export default defineCommand({
3738 examples : [
3839 'mmx speech synthesize --text "Hello, world!"' ,
3940 'mmx speech synthesize --text "Hello, world!" --out hello.mp3' ,
41+ 'mmx speech synthesize --text "Hello" --subtitles --out hello.mp3' ,
4042 'echo "Breaking news." | mmx speech synthesize --text-file - --out news.mp3' ,
4143 'mmx speech synthesize --text "Stream" --stream | mpv --no-terminal -' ,
4244 ] ,
@@ -85,7 +87,7 @@ export default defineCommand({
8587 } ;
8688
8789 if ( flags . language ) body . language_boost = flags . language as string ;
88- if ( flags . subtitles ) body . subtitle = true ;
90+ if ( flags . subtitles ) body . subtitle_enable = true ; // Correct API parameter name
8991
9092 if ( flags . pronunciation ) {
9193 body . pronunciation_dict = ( flags . pronunciation as string [ ] ) . map ( p => {
@@ -122,5 +124,52 @@ export default defineCommand({
122124
123125 if ( ! config . quiet ) process . stderr . write ( `[Model: ${ model } ]\n` ) ;
124126 saveAudioOutput ( response , outPath , format , config . quiet ) ;
127+
128+ // Download and save subtitle file when --subtitles is requested
129+ if ( flags . subtitles && response . data . subtitle_file ) {
130+ try {
131+ // Download the subtitle JSON file from the URL
132+ const subtitleRes = await fetch ( response . data . subtitle_file ) ;
133+ if ( ! subtitleRes . ok ) {
134+ throw new CLIError ( `Failed to download subtitle file: ${ subtitleRes . status } ` , ExitCode . GENERAL ) ;
135+ }
136+ // API returns a flat array, not { subtitles: [...] }
137+ const subtitleArray = await subtitleRes . json ( ) as Array < { text : string ; time_begin : number ; time_end : number } > ;
138+
139+ if ( subtitleArray ?. length ) {
140+ // Convert to SRT format (API returns time in milliseconds)
141+ const subtitlePath = outPath . replace ( / \. [ ^ . ] + $ / , '' ) + '.srt' ;
142+ const srtContent = subtitleArray
143+ . map ( ( s , i ) => {
144+ // API already returns milliseconds, use directly
145+ const fmt = ( ms : number ) => {
146+ const h = String ( Math . floor ( ms / 3600000 ) ) . padStart ( 2 , '0' ) ;
147+ const m = String ( Math . floor ( ( ms % 3600000 ) / 60000 ) ) . padStart ( 2 , '0' ) ;
148+ const sec = String ( Math . floor ( ( ms % 60000 ) / 1000 ) ) . padStart ( 2 , '0' ) ;
149+ const mil = String ( Math . round ( ms % 1000 ) ) . padStart ( 3 , '0' ) ;
150+ return `${ h } :${ m } :${ sec } ,${ mil } ` ;
151+ } ;
152+ return `${ i + 1 } \n${ fmt ( s . time_begin ) } --> ${ fmt ( s . time_end ) } \n${ s . text } ` ;
153+ } )
154+ . join ( '\n\n' ) ;
155+ writeFileSync ( subtitlePath , srtContent , 'utf-8' ) ;
156+ if ( ! config . quiet ) {
157+ console . log ( formatOutput ( { subtitles : subtitlePath } , format ) ) ;
158+ } else {
159+ console . log ( subtitlePath ) ;
160+ }
161+ }
162+ } catch ( err ) {
163+ // Non-fatal: log warning but don't fail the whole synthesis
164+ if ( ! config . quiet ) {
165+ process . stderr . write ( `Warning: failed to download subtitles: ${ ( err as Error ) . message } \n` ) ;
166+ }
167+ }
168+ } else if ( flags . subtitles && ! response . data . subtitle_file ) {
169+ // Warn if --subtitles was requested but API didn't return subtitle_file
170+ if ( ! config . quiet ) {
171+ process . stderr . write ( `Warning: subtitles requested but not returned by API\n` ) ;
172+ }
173+ }
125174 } ,
126175} ) ;
0 commit comments