@@ -86,6 +86,78 @@ IPipWheelService pipWheelService
8686 /// </summary>
8787 private bool IsAmdRocm => GetRecommendedTorchVersion ( ) == TorchIndex . Rocm ;
8888
89+ /// <summary>
90+ /// Python wrapper script that patches logging to also print to stdout/stderr, so
91+ /// StabilityMatrix can capture the output. Wan2GP logs through Gradio UI notifications
92+ /// (gr.Info/Warning/Error) and callback-driven UI updates that never reach the console.
93+ /// This script:
94+ /// 1. Configures Python's logging module to output to stderr (captures library logging)
95+ /// 2. Prevents transformers from suppressing its own logging (wgp.py calls set_verbosity_error)
96+ /// 3. Monkey-patches gr.Info/Warning/Error to also print to stdout/stderr
97+ /// 4. Runs the target script (wgp.py) via runpy
98+ /// </summary>
99+ private const string GradioLogPatchScript = """
100+ # StabilityMatrix: Patch logging to print to console for capture.
101+ import sys
102+ import logging
103+
104+ def _apply_logging_patch():
105+ # Configure Python's root logger to output to stderr at INFO level.
106+ # Many libraries (torch, diffusers, transformers, etc.) use the logging
107+ # module but output may be suppressed without a handler configured.
108+ root = logging.getLogger()
109+ if not any(isinstance(h, logging.StreamHandler) for h in root.handlers):
110+ handler = logging.StreamHandler(sys.stderr)
111+ handler.setFormatter(logging.Formatter("[%(name)s] %(levelname)s: %(message)s"))
112+ root.addHandler(handler)
113+ if root.level > logging.INFO:
114+ root.setLevel(logging.INFO)
115+
116+ # Prevent transformers from suppressing its own logging.
117+ # wgp.py calls transformers.utils.logging.set_verbosity_error() which
118+ # silences all non-error messages. We neutralize those calls so model
119+ # loading and download messages remain visible.
120+ try:
121+ import transformers.utils.logging as tf_logging
122+ tf_logging.set_verbosity_error = lambda: None
123+ tf_logging.set_verbosity_warning = lambda: None
124+ tf_logging.set_verbosity(logging.INFO)
125+ except Exception as e:
126+ print(f"[StabilityMatrix] Failed to patch transformers logging: {e}", file=sys.stderr, flush=True)
127+
128+ # Monkey-patch Gradio's UI notification functions to also print to console.
129+ # These only fire for validation/error messages, not generation progress.
130+ try:
131+ import gradio as gr
132+ _orig_info = getattr(gr, 'Info', None)
133+ _orig_warning = getattr(gr, 'Warning', None)
134+ _orig_error = getattr(gr, 'Error', None)
135+ if _orig_info is not None:
136+ def patched_info(message, *args, **kwargs):
137+ print(f"[Gradio] {message}", flush=True)
138+ return _orig_info(message, *args, **kwargs)
139+ gr.Info = patched_info
140+ if _orig_warning is not None:
141+ def patched_warning(message, *args, **kwargs):
142+ print(f"[Gradio] WARNING: {message}", flush=True)
143+ return _orig_warning(message, *args, **kwargs)
144+ gr.Warning = patched_warning
145+ if _orig_error is not None:
146+ def patched_error(message, *args, **kwargs):
147+ print(f"[Gradio] ERROR: {message}", file=sys.stderr, flush=True)
148+ return _orig_error(message, *args, **kwargs)
149+ gr.Error = patched_error
150+ except Exception as e:
151+ print(f"[StabilityMatrix] Failed to patch Gradio logging: {e}", file=sys.stderr, flush=True)
152+
153+ if __name__ == "__main__":
154+ _apply_logging_patch()
155+ target_script = sys.argv[1]
156+ sys.argv = sys.argv[1:]
157+ import runpy
158+ runpy.run_path(target_script, run_name="__main__")
159+ """ ;
160+
89161 public override List < LaunchOptionDefinition > LaunchOptions =>
90162 [
91163 new ( )
@@ -368,13 +440,22 @@ await SetupVenv(installLocation, pythonVersion: PyVersion.Parse(installedPackage
368440 // Fix for distutils compatibility issue with Python 3.10 and setuptools
369441 VenvRunner . UpdateEnvironmentVariables ( env => env . SetItem ( "SETUPTOOLS_USE_DISTUTILS" , "stdlib" ) ) ;
370442
443+ // Write the Gradio logging patch wrapper script so gr.Info/Warning/Error
444+ // messages are also printed to stdout/stderr for console capture
445+ var patchScriptPath = Path . Combine ( installLocation , "_sm_gradio_log_patch.py" ) ;
446+ await File . WriteAllTextAsync ( patchScriptPath , GradioLogPatchScript , cancellationToken )
447+ . ConfigureAwait ( false ) ;
448+
449+ var targetScript = Path . Combine ( installLocation , options . Command ?? LaunchCommand ) ;
450+
371451 // Notify user that the package is starting (loading can take a while)
372452 onConsoleOutput ? . Invoke (
373453 new ProcessOutput { Text = "Launching Wan2GP, please wait while the UI initializes...\n " }
374454 ) ;
375455
456+ // Launch via the patch wrapper, which monkey-patches Gradio then runs wgp.py
376457 VenvRunner . RunDetached (
377- [ Path . Combine ( installLocation , options . Command ?? LaunchCommand ) , .. options . Arguments ] ,
458+ [ patchScriptPath , targetScript , .. options . Arguments ] ,
378459 HandleConsoleOutput ,
379460 OnExit
380461 ) ;
0 commit comments