import base64
import code
import io
import re
import sys
from contextlib import redirect_stderr, redirect_stdout
# import matplotlib.pyplot as plt
import numpy as np
import torch
from aiohttp import web
from PIL import Image
from rich.console import Console
from rich.traceback import Traceback
from .log import log
try:
import pyflakes.api
import pyflakes.reporter
_HAS_LINT = True
except ImportError:
print(
"ComfyREPL: pyflakes not found. Linting will be disabled. Install with 'pip install pyflakes'."
)
_HAS_LINT = False
# --- Linting Library ---
# try:
# import ruff
# import ruff.lint
# import ruff.lint.linter
# import ruff.settings
#
# _HAS_LINT = True
# except ImportError:
# print(
# "ComfyREPL: ruff not found. Linting will be disabled. Install with 'pip install ruff'."
# )
# _HAS_LINT = False
# --- Audio/Video Libraries ---
try:
import scipy.io.wavfile
_HAS_SCIPY = True
except ImportError:
print(
"ComfyREPL: SciPy not found. Audio display will be disabled. Install with 'pip install scipy'."
)
_HAS_SCIPY = False
try:
import imageio
import imageio.plugins.ffmpeg # Ensure ffmpeg plugin is available
_HAS_IMAGEIO = True
except ImportError:
print(
"ComfyREPL: Imageio or imageio-ffmpeg not found. Video display will be disabled. Install with 'pip install imageio imageio-ffmpeg'."
)
_HAS_IMAGEIO = False
# --- Audio Display ---
class AudioDisplay:
def __init__(self, samples, sample_rate):
if not _HAS_SCIPY:
raise ImportError("Audio display requires scipy and numpy.")
if not isinstance(samples, (np.ndarray, torch.Tensor)):
raise TypeError(
"Audio samples must be a numpy array or torch tensor."
)
if isinstance(samples, torch.Tensor):
samples = samples.detach().cpu().numpy()
# Ensure samples are in a format scipy.io.wavfile can handle (e.g., int16, float32)
if samples.dtype == np.float64:
samples = samples.astype(np.float32)
elif samples.dtype == np.int64:
# Or scale to int32 if range requires
samples = samples.astype(np.int16)
self.samples = samples
self.sample_rate = sample_rate
def _to_wav_base64(self):
buffer = io.BytesIO()
try:
scipy.io.wavfile.write(buffer, self.sample_rate, self.samples)
audio_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
return audio_base64
except Exception as e:
return f"
Error encoding audio: {e}
"
def _repr_html_(self):
base64_data = self._to_wav_base64()
if base64_data.startswith("'
def render_audio(samples, sample_rate):
"""
Render audio samples as an HTML audio player.
Args:
samples (np.ndarray or torch.Tensor): Audio samples.
sample_rate (int): Sample rate in Hz.
Returns
-------
AudioDisplay: An object that will render as an HTML audio player.
"""
return AudioDisplay(samples, sample_rate)
# --- Display Classes ---
class VideoDisplay:
def __init__(self, frames, fps=24, options=None):
if not _HAS_IMAGEIO: # numpy/PIL/torch needed for frames
raise ImportError(
"Video display requires imageio, imageio-ffmpeg, and image libraries (numpy, Pillow, torch)."
)
self.frames = []
for frame in frames:
if isinstance(frame, Image.Image):
self.frames.append(np.array(frame))
elif isinstance(frame, np.ndarray):
# Ensure HWC and uint8
if frame.ndim == 3 and frame.shape[0] in [1, 3, 4]: # CHW
frame = np.transpose(frame, (1, 2, 0))
if frame.dtype != np.uint8:
frame = (
(frame * 255).astype(np.uint8)
if frame.max() <= 1.0
else frame.astype(np.uint8)
)
self.frames.append(frame)
elif isinstance(frame, torch.Tensor):
np_frame = frame.detach().cpu().numpy()
if np_frame.ndim == 3 and np_frame.shape[0] in [
1,
3,
4,
]: # CHW
np_frame = np.transpose(np_frame, (1, 2, 0))
if np_frame.dtype != np.uint8:
np_frame = (
(np_frame * 255).astype(np.uint8)
if np_frame.max() <= 1.0
else np_frame.astype(np.uint8)
)
self.frames.append(np_frame)
else:
raise TypeError(
f"Unsupported frame type: {type(frame)}. Must be PIL.Image, numpy.ndarray, or torch.Tensor."
)
self.fps = fps
self.options = options if options is not None else {}
def _to_mp4_base64(self):
buffer = io.BytesIO()
try:
# Use imageio to write frames to an in-memory MP4 file
imageio.mimwrite(
buffer,
self.frames,
format="mp4",
fps=self.fps,
codec="libx264",
quality=8,
) # quality 1-10
video_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
return video_base64
except Exception as e:
return f"Error encoding video: {e}
"
def _repr_html_(self):
base64_data = self._to_mp4_base64()
if base64_data.startswith("'
def render_video(batch_tensor_or_array_of_pil_images, fps=24, options=None):
"""
Render video frames as an HTML video player.
Args:
batch_tensor_or_array_of_pil_images (list of PIL.Image, np.ndarray, or torch.Tensor):
A list of frames, or a single batch tensor/array (B, H, W, C) or (B, C, H, W).
fps (int): Frames per second.
options (dict): Dictionary of HTML