import os import server import yaml import nodes as _nodes from typing_extensions import override from comfy_api.latest import ComfyExtension, io from aiohttp import web from .nodes import * from .routes import * from .utils.bernini_s2v_model_patch import apply_bernini_s2v_model_patches from .utils.log import install_console_privacy_filter from .moxie_loader import MoxieMultimediaLoader from .moxie_project import MoxieProject from .moxie_combine import MoxieCombine from .moxie_preview import ModelPreviewOverrideKJ as MoxiePreviewOverride apply_bernini_s2v_model_patches() # Keep internal model/vendor names out of the console (incl. ComfyUI core logs). install_console_privacy_filter() # Define the path root_path = os.path.dirname(__file__) project_name = os.path.basename(root_path) web_default_version = 'release' data = None config_path = os.path.join(root_path, "config.yaml") if os.path.isfile(config_path): with open(config_path, 'r') as f: data = yaml.load(f, Loader=yaml.FullLoader) if data and "WEB_VERSION" in data: dist_path = f"dist/{data['WEB_VERSION']}" else: dist_path = f"dist/{web_default_version}" else: dist_path = f"dist/{web_default_version}" web_version = data.get("WEB_VERSION", web_default_version) if data else web_default_version if not os.path.exists(os.path.join(root_path, dist_path)): print(f"web root {web_version} not found, using default") dist_path = f"dist/{web_default_version}" dist_path = os.path.join(root_path, dist_path) if os.path.exists(dist_path): # Editor delivery (see web/js/editor-loader.js for the full story): # core lets WEB_DIRECTORY (./web) overwrite this pack's EXTENSION_WEB_DIRS # entry, so the browser /extensions listing only contains the helper JS in # web/js - never dist/release. web/js/editor-loader.js is listed instead # and imports the editor entry from this pack's own static mount below. # # The entry file (index.js) keeps a stable URL across releases, so it is # served with no-cache and always revalidated (304 when unchanged); its # relative chunk imports resolve against the same mount and are # content-hashed, so those may be cached forever. async def _serve_editor_index(request: web.Request) -> web.StreamResponse: return web.FileResponse( os.path.join(dist_path, 'index.js'), headers={'Cache-Control': 'no-cache, must-revalidate'}, ) server.PromptServer.instance.app.add_routes([ # Do NOT register "/extensions//index.js" here: relative chunk # imports under that URL would resolve into web/ (core's mount) and # 404. The loader imports "//index.js" from this mount instead. web.get(f"/{project_name}/index.js", _serve_editor_index), web.static(f"/{project_name}/", dist_path), ]) else: print(f"Warning: Dist directory not found for {project_name}. Frontend assets will be served.") # Our own frontend assets (Moxie Preview Override widget) WEB_DIRECTORY = "./web" # Register extension class EasyMediaExtension(ComfyExtension): @override async def get_node_list(self) -> list[type[io.ComfyNode]]: nodes = [ # Moxie consolidated nodes MoxieMultimediaLoader, MoxiePreviewOverride, MoxieProject, MoxieCombine, # TimelineEditor TimelineEditor, TimelineInfoOutput, TimelineSegmentOutput, TimelineSegmentCount, # MultiTrack MultiTrackEditor, MultiImagesLoader, MultiTrackTaskOutput, MultiTrackPromptEnhancer, MultiTrackPromptEnhanceToProject, MultiTrackPromptEnhanceToProjectApply, MultiTrackPromptEnhancerImageListBridge, MultiTrackInfoOutput, MultiTrackAudioOutput, # Subtitle MultiTrackAddSubtitleToVideo, RecognizeSubtitle, AddSubtitleToVideo, # Image MakeRefsCompositeBySam3, ImageIndexesToIntList, # Audio EasyAudioMerge, EasyMinimaxH3AudioLock, # Video EasySaveVideo, EasyCompareVideos, EasyGetAudioFromVideo, EasyMergeVideos, EasyMergeVideosFromPaths, # Split SplitImages, SplitAudios, SplitVideos, # Make List MakeImageList, MakeAudioList, MakeVideoList, # Common EasyModelLoaderPack, MatchLine, APIWorkflowGate, # MiniMax EasyMiniMaxH3MotionContextHard, EasyMiniMaxH3ContextSwap, EasyMiniMaxH3HiResContinuity, EasyH3MotionContextLatentTrim, EasyH3ProjectContextLatentLoad, EasyH3ConditioningCache, EasyH3SegmentSamplingStart, EasyH3SamplingPreviewSampler, EasyMiniMaxH3LatentUpscaler, EasyMiniMaxH3SelfLiftSampler, EasyH3SegmentSaveEnd, EasyH3ContextMediaTrim, EasyH3PassthroughVideo, EasyH3AudioContextLatent, EasyH3LockedAudioDurationAlign, EasyH3LockedAudioSelect, EasyH3ProjectArtifact, EasyH3ProjectStaticPrepare, EasyMultiTrackProject, EasyMultiTrackProjectVideoCombine, EasyMiniMaxH3ToVideo, EasyMinimaxPromptOverride, EasyMiniMaxH3ReferenceToVideoBridge, EasyRemoveH3MotionContextLatent, # Wan BerniniModelPatch, EasyBerniniS2VConditioning, # LTXV LTXMultiTrackEncode, LTXI2VInplaceAndUpsample, LTXSamplerSimple, LTXVAddGuidesFromBatchIndexes, LTXVMakeRefVideo, ] try: from comfy_extras.nodes_bernini import BerniniConditioning as CoreBerniniConditioning except ImportError: nodes.extend([BerniniConditioning]) nodes.extend(get_minimax_h3_fallback_nodes()) return nodes async def comfy_entrypoint() -> EasyMediaExtension: return EasyMediaExtension()