@@ -5,19 +5,12 @@ import { requestJson } from '../../client/http';
55import { videoGenerateEndpoint , videoTaskEndpoint , fileRetrieveEndpoint } from '../../client/endpoints' ;
66import { poll } from '../../polling/poll' ;
77import { downloadFile , formatBytes } from '../../files/download' ;
8- import { formatOutput , detectOutputFormat } from '../../output/formatter' ;
8+ import { formatOutput , detectOutputFormat , dryRun } from '../../output/formatter' ;
99import type { Config } from '../../config/schema' ;
1010import type { GlobalFlags } from '../../types/flags' ;
1111import type { VideoRequest , VideoResponse , VideoTaskResponse , FileRetrieveResponse } from '../../types/api' ;
12- import { readFileSync } from 'fs' ;
13- import { extname } from 'path' ;
14-
15- const MIME_TYPES : Record < string , string > = {
16- '.jpg' : 'image/jpeg' , '.jpeg' : 'image/jpeg' ,
17- '.png' : 'image/png' , '.webp' : 'image/webp' ,
18- } ;
19- import { isInteractive } from '../../utils/env' ;
20- import { promptText , failIfMissing } from '../../utils/prompt' ;
12+ import { resolveImageInput } from '../../utils/image' ;
13+ import { promptOrFail } from '../../utils/prompt' ;
2114
2215export default defineCommand ( {
2316 name : 'video generate' ,
@@ -49,18 +42,14 @@ export default defineCommand({
4942 async run ( config : Config , flags : GlobalFlags ) {
5043 let prompt = flags . prompt as string | undefined ;
5144
52- if ( ! prompt ) {
53- if ( isInteractive ( { nonInteractive : config . nonInteractive } ) ) {
54- const hint = await promptText ( { message : 'Enter your video prompt:' } ) ;
55- if ( ! hint ) {
56- process . stderr . write ( 'Video generation cancelled.\n' ) ;
57- process . exit ( 1 ) ;
58- }
59- prompt = hint ;
60- } else {
61- failIfMissing ( 'prompt' , 'mmx video generate --prompt <text>' ) ;
62- }
63- }
45+ prompt = await promptOrFail ( {
46+ value : prompt ,
47+ message : 'Enter your video prompt:' ,
48+ cancelMessage : 'Video generation cancelled.' ,
49+ flagName : 'prompt' ,
50+ usageHint : 'mmx video generate --prompt <text>' ,
51+ nonInteractive : config . nonInteractive ,
52+ } ) ;
6453
6554 // Validate mutually exclusive mode flags
6655 if ( flags . lastFrame && flags . subjectImage ) {
@@ -92,7 +81,6 @@ export default defineCommand({
9281 } else {
9382 model = config . defaultVideoModel || 'MiniMax-Hailuo-2.3' ;
9483 }
95- const format = detectOutputFormat ( config . output ) ;
9684
9785 const body : VideoRequest = {
9886 model,
@@ -101,15 +89,7 @@ export default defineCommand({
10189
10290 // First frame (I2V)
10391 if ( flags . firstFrame ) {
104- const framePath = flags . firstFrame as string ;
105- if ( framePath . startsWith ( 'http' ) ) {
106- body . first_frame_image = framePath ;
107- } else {
108- const imgData = readFileSync ( framePath ) ;
109- const ext = extname ( framePath ) . toLowerCase ( ) ;
110- const mime = MIME_TYPES [ ext ] || 'image/jpeg' ;
111- body . first_frame_image = `data:${ mime } ;base64,${ imgData . toString ( 'base64' ) } ` ;
112- }
92+ body . first_frame_image = resolveImageInput ( flags . firstFrame as string ) ;
11393 }
11494
11595 // Last frame (SEF mode)
@@ -121,41 +101,21 @@ export default defineCommand({
121101 'mmx video generate --prompt <text> --first-frame <path> --last-frame <path>' ,
122102 ) ;
123103 }
124- const framePath = flags . lastFrame as string ;
125- if ( framePath . startsWith ( 'http' ) ) {
126- body . last_frame_image = framePath ;
127- } else {
128- const imgData = readFileSync ( framePath ) ;
129- const ext = extname ( framePath ) . toLowerCase ( ) ;
130- const mime = MIME_TYPES [ ext ] || 'image/jpeg' ;
131- body . last_frame_image = `data:${ mime } ;base64,${ imgData . toString ( 'base64' ) } ` ;
132- }
104+ body . last_frame_image = resolveImageInput ( flags . lastFrame as string ) ;
133105 }
134106
135107 // Subject reference (S2V mode)
136108 if ( flags . subjectImage ) {
137- const imgPath = flags . subjectImage as string ;
138- let imageData : string ;
139- if ( imgPath . startsWith ( 'http' ) ) {
140- imageData = imgPath ;
141- } else {
142- const imgData = readFileSync ( imgPath ) ;
143- const ext = extname ( imgPath ) . toLowerCase ( ) ;
144- const mime = MIME_TYPES [ ext ] || 'image/jpeg' ;
145- imageData = `data:${ mime } ;base64,${ imgData . toString ( 'base64' ) } ` ;
146- }
147- body . subject_reference = [ { type : 'character' , image : [ imageData ] } ] ;
109+ body . subject_reference = [ { type : 'character' , image : [ resolveImageInput ( flags . subjectImage as string ) ] } ] ;
148110 }
149111
150112 if ( flags . callbackUrl ) {
151113 body . callback_url = flags . callbackUrl as string ;
152114 }
153115
154- if ( config . dryRun ) {
155- console . log ( formatOutput ( { request : body } , format ) ) ;
156- return ;
157- }
116+ if ( dryRun ( config , body ) ) return ;
158117
118+ const format = detectOutputFormat ( config . output ) ;
159119 const url = videoGenerateEndpoint ( config . baseUrl ) ;
160120 const response = await requestJson < VideoResponse > ( config , {
161121 url,
0 commit comments