@@ -83,35 +83,104 @@ def _session(root: Path, callbacks: object | None = None) -> Any:
8383 )
8484
8585
86- def _video_models (session : Any ) -> list [dict [str , Any ]]:
86+ _MODEL_TASKS = {
87+ "video" : ("text_to_video" , "image_to_video" ),
88+ "image" : ("text_to_image" , "image_to_image" ),
89+ }
90+
91+
92+ def _is_music (metadata : dict [str , Any ]) -> bool :
93+ return str (metadata .get ("family" ) or "" ).casefold () == "music"
94+
95+
96+ def _choice_values (definition : Any ) -> list [str ]:
97+ if not isinstance (definition , dict ):
98+ return []
99+ values : list [str ] = []
100+ for choice in definition .get ("choices" , []):
101+ value = choice .get ("value" ) if isinstance (choice , dict ) else choice
102+ if value is not None and str (value ):
103+ values .append (str (value ))
104+ return values
105+
106+
107+ def _models (session : Any , model_kind : str ) -> list [dict [str , Any ]]:
108+ """Translate WanGP model metadata into NodeTool provider models."""
109+ if model_kind not in {"video" , "image" , "tts" , "music" }:
110+ return []
87111 models : list [dict [str , Any ]] = []
88112 for metadata in session .list_model_metadata ():
89- outputs = metadata .get ("outputs" , [])
90- if "video" not in outputs :
113+ outputs = metadata .get ("main_output" , metadata .get ("outputs" , []))
114+ expected_output = "audio" if model_kind in {"tts" , "music" } else model_kind
115+ if expected_output not in outputs :
116+ continue
117+ if model_kind == "music" and not _is_music (metadata ):
118+ continue
119+ if model_kind == "tts" and _is_music (metadata ):
91120 continue
92121 capabilities = metadata .get ("capabilities" , {})
93- supported = [
94- task
95- for task in ("text_to_video" , "image_to_video" )
96- if capabilities .get (task )
97- ]
122+ if model_kind in _MODEL_TASKS :
123+ supported = [
124+ task for task in _MODEL_TASKS [model_kind ] if capabilities .get (task )
125+ ]
126+ elif model_kind == "music" :
127+ supported = ["text_to_music" ] if capabilities .get ("text_to_audio" ) else []
128+ else :
129+ supported = ["text_to_speech" ] if capabilities .get ("text_to_audio" ) else []
98130 if not supported :
99131 continue
100132 model_type = str (metadata .get ("model_type" ) or "" ).strip ()
101133 if not model_type :
102134 continue
103- models .append (
104- {
105- "id" : model_type ,
106- "name" : str (metadata .get ("name" ) or model_type ),
107- "provider" : "wangp" ,
108- "supportedTasks" : supported ,
109- }
110- )
135+ model = {
136+ "id" : model_type ,
137+ "name" : str (metadata .get ("name" ) or model_type ),
138+ "provider" : "wangp" ,
139+ }
140+ if model_kind == "tts" :
141+ base_model_type = str (metadata .get ("base_model_type" ) or model_type )
142+ tts_capabilities = list (supported )
143+ media_inputs = metadata .get ("media_inputs" )
144+ audio_inputs = (
145+ media_inputs .get ("audio" , {}) if isinstance (media_inputs , dict ) else {}
146+ )
147+ if audio_inputs .get ("prompt" ):
148+ tts_capabilities .append ("voice_cloning" )
149+ if base_model_type in {"qwen3_tts_base" , "omnivoice" }:
150+ tts_capabilities .append ("reference_transcript" )
151+ if base_model_type in {
152+ "qwen3_tts_customvoice" ,
153+ "index_tts2" ,
154+ "index_tts25" ,
155+ }:
156+ tts_capabilities .append ("instruction_control" )
157+ if base_model_type in {"qwen3_tts_voicedesign" , "omnivoice" }:
158+ tts_capabilities .append ("voice_design" )
159+
160+ setting_values = metadata .get ("setting_values" )
161+ model_mode = (
162+ setting_values .get ("model_mode" )
163+ if isinstance (setting_values , dict )
164+ else None
165+ )
166+ mode_label = str (
167+ model_mode .get ("label" , "" ) if isinstance (model_mode , dict ) else ""
168+ ).casefold ()
169+ mode_values = _choice_values (model_mode )
170+ if mode_label == "speaker" :
171+ tts_capabilities .append ("preset_voice" )
172+ model ["voices" ] = mode_values
173+ elif mode_label == "language" :
174+ tts_capabilities .append ("language_selection" )
175+ model ["languages" ] = mode_values
176+ model ["capabilities" ] = list (dict .fromkeys (tts_capabilities ))
177+ else :
178+ model ["supportedTasks" ] = supported
179+ models .append (model )
111180 return models
112181
113182
114- def _settings (request : dict [str , Any ]) -> dict [ str , Any ] :
183+ def _model_id (request : dict [str , Any ]) -> str :
115184 params = request .get ("params" )
116185 if not isinstance (params , dict ):
117186 raise ValueError ("Provider generation requires params" )
@@ -121,33 +190,119 @@ def _settings(request: dict[str, Any]) -> dict[str, Any]:
121190 model_type = str (model or "" ).strip ()
122191 if not model_type :
123192 raise ValueError ("Provider generation requires a model id" )
193+ return model_type
194+
195+
196+ def _input_path (request : dict [str , Any ], name : str ) -> Path :
197+ path = Path (str (request .get (name ) or "" )).resolve ()
198+ if not path .is_file ():
199+ raise ValueError (f"{ request .get ('operation' )} requires a readable { name } " )
200+ return path
201+
202+
203+ def _setting_choice_with_flag (
204+ metadata : dict [str , Any ] | None , setting : str , flag : str
205+ ) -> str :
206+ definitions = (metadata or {}).get ("setting_values" , {}).get (
207+ "video_prompt_type" , {}
208+ )
209+ choice_def = definitions .get (setting )
210+ if not isinstance (choice_def , dict ):
211+ return ""
212+ for choice in choice_def .get ("choices" , []):
213+ value = choice .get ("value" , "" ) if isinstance (choice , dict ) else ""
214+ if flag in str (value ):
215+ return str (value )
216+ return ""
217+
218+
219+ def _settings (
220+ request : dict [str , Any ], metadata : dict [str , Any ] | None = None
221+ ) -> dict [str , Any ]:
222+ params = request ["params" ]
223+ operation = str (request .get ("operation" ) or "" )
224+ model_type = _model_id (request )
124225
125226 settings : dict [str , Any ] = {
126227 "model_type" : model_type ,
127- "prompt" : str (params .get ("prompt" ) or "" ),
228+ "prompt" : str (params .get ("text" ) or params . get ( " prompt" ) or "" ),
128229 }
129230 mappings = {
130231 "negativePrompt" : "negative_prompt" ,
131232 "resolution" : "resolution" ,
132233 "guidanceScale" : "guidance_scale" ,
133234 "numInferenceSteps" : "num_inference_steps" ,
134235 "seed" : "seed" ,
236+ "fps" : "force_fps" ,
237+ "scheduler" : "sample_solver" ,
238+ "strength" : "denoising_strength" ,
135239 }
136240 for source , target in mappings .items ():
137241 value = params .get (source )
138242 if value is not None :
139243 settings [target ] = value
244+ if "resolution" not in settings :
245+ width = params .get ("width" , params .get ("targetWidth" ))
246+ height = params .get ("height" , params .get ("targetHeight" ))
247+ if width is not None and height is not None :
248+ settings ["resolution" ] = f"{ int (width )} x{ int (height )} "
140249 if params .get ("numFrames" ) is not None :
141250 settings ["video_length" ] = int (params ["numFrames" ])
142- elif params .get ("durationSeconds" ) is not None :
251+ elif params .get ("durationSeconds" ) is not None and operation . endswith ( "video" ) :
143252 settings ["video_length" ] = f"{ float (params ['durationSeconds' ]):g} s"
144253
145- if request ["operation" ] == "image_to_video" :
146- image_path = Path (str (request .get ("image_path" ) or "" )).resolve ()
147- if not image_path .is_file ():
148- raise ValueError ("image_to_video requires a readable image_path" )
149- settings ["image_start" ] = str (image_path )
150- settings ["image_prompt_type" ] = "S"
254+ if operation in {"text_to_image" , "image_to_image" }:
255+ settings ["image_mode" ] = 1
256+
257+ if operation in {"image_to_image" , "image_to_video" }:
258+ image_path = str (_input_path (request , "image_path" ))
259+ image_inputs = (metadata or {}).get ("media_inputs" , {}).get ("image" , {})
260+ if image_inputs .get ("start" ) or operation == "image_to_video" :
261+ settings ["image_start" ] = image_path
262+ settings ["image_prompt_type" ] = "S"
263+ elif image_inputs .get ("reference" ):
264+ settings ["image_refs" ] = [image_path ]
265+ elif image_inputs .get ("control" ):
266+ settings ["image_guide" ] = image_path
267+ control_mode = _setting_choice_with_flag (
268+ metadata , "guide_preprocessing" , "V"
269+ ) or _setting_choice_with_flag (metadata , "guide_custom_choices" , "V" )
270+ if control_mode :
271+ settings ["video_prompt_type" ] = control_mode
272+ else :
273+ raise ValueError (f"Model { model_type } does not accept an input image" )
274+
275+ if operation == "text_to_audio" :
276+ style_prompt = str (params .get ("prompt" ) or "" )
277+ lyrics = str (params .get ("lyrics" ) or "" ).strip ()
278+ base_model_type = str ((metadata or {}).get ("base_model_type" ) or model_type )
279+ if base_model_type .startswith ("stable_audio3" ):
280+ settings ["prompt" ] = style_prompt
281+ else :
282+ settings ["prompt" ] = lyrics or "[Instrumental]"
283+ settings ["alt_prompt" ] = style_prompt
284+ if params .get ("durationSeconds" ) is not None :
285+ settings ["duration_seconds" ] = float (params ["durationSeconds" ])
286+
287+ if operation == "tts_encoded" :
288+ if params .get ("referenceText" ) is not None :
289+ settings ["alt_prompt" ] = str (params ["referenceText" ])
290+ elif params .get ("instructions" ) is not None :
291+ settings ["alt_prompt" ] = str (params ["instructions" ])
292+ base_model_type = str ((metadata or {}).get ("base_model_type" ) or model_type )
293+ model_mode = (
294+ params .get ("voice" )
295+ if base_model_type == "qwen3_tts_customvoice"
296+ else params .get ("language" )
297+ )
298+ if model_mode :
299+ settings ["model_mode" ] = str (model_mode )
300+ if params .get ("speed" ) is not None and base_model_type == "index_tts25" :
301+ settings ["custom_settings" ] = {"speech_speed" : float (params ["speed" ])}
302+ if request .get ("reference_audio_path" ):
303+ settings ["audio_guide" ] = str (
304+ _input_path (request , "reference_audio_path" )
305+ )
151306 return settings
152307
153308
@@ -168,19 +323,30 @@ def on_progress(self, update: Any) -> None:
168323 )
169324
170325
171- def _generated_path (result : Any ) -> str :
326+ def _generated_path (result : Any , media_type : str | None = None ) -> str :
172327 if not getattr (result , "success" , False ):
173328 errors = getattr (result , "errors" , ())
174329 detail = "; " .join (str (error ) for error in errors ) or "generation failed"
175330 raise RuntimeError (detail )
176- for path in getattr (result , "generated_files" , ()):
177- candidate = Path (str (path ))
178- if candidate .is_file ():
179- return str (candidate .resolve ())
180331 for artifact in getattr (result , "artifacts" , ()):
332+ if media_type and getattr (artifact , "media_type" , None ) != media_type :
333+ continue
181334 path = getattr (artifact , "path" , None )
182335 if path and Path (path ).is_file ():
183336 return str (Path (path ).resolve ())
337+ media_suffixes = {
338+ "image" : {".bmp" , ".gif" , ".jpeg" , ".jpg" , ".png" , ".tif" , ".tiff" , ".webp" },
339+ "video" : {".avi" , ".mkv" , ".mov" , ".mp4" , ".webm" },
340+ "audio" : {".aac" , ".flac" , ".m4a" , ".mp3" , ".ogg" , ".opus" , ".wav" },
341+ }
342+ expected_suffixes = media_suffixes .get (media_type or "" )
343+ for path in getattr (result , "generated_files" , ()):
344+ candidate = Path (str (path ))
345+ if candidate .is_file () and (
346+ expected_suffixes is None
347+ or candidate .suffix .casefold () in expected_suffixes
348+ ):
349+ return str (candidate .resolve ())
184350 raise RuntimeError ("WanGP completed without a generated media file" )
185351
186352
@@ -196,14 +362,24 @@ def main() -> int:
196362 with contextlib .redirect_stdout (sys .stderr ):
197363 if operation == "models" :
198364 model_type = str (request .get ("model_type" ) or "" )
199- models = _video_models (_session (root )) if model_type == "video" else []
365+ models = _models (_session (root ), model_type )
200366 emitter .send ("result" , {"models" : models })
201367 return 0
202- if operation not in {"text_to_video" , "image_to_video" }:
368+ media_type = {
369+ "text_to_image" : "image" ,
370+ "image_to_image" : "image" ,
371+ "text_to_video" : "video" ,
372+ "image_to_video" : "video" ,
373+ "text_to_audio" : "audio" ,
374+ "tts_encoded" : "audio" ,
375+ }.get (operation )
376+ if media_type is None :
203377 raise ValueError (f"Unsupported provider operation: { operation } " )
204378 callbacks = _Callbacks (emitter )
205- result = _session (root , callbacks ).run_task (_settings (request ))
206- emitter .send ("result" , {"path" : _generated_path (result )})
379+ session = _session (root , callbacks )
380+ metadata = session .get_model_metadata (_model_id (request ))
381+ result = session .run_task (_settings (request , metadata ))
382+ emitter .send ("result" , {"path" : _generated_path (result , media_type )})
207383 return 0
208384
209385
0 commit comments