diff --git a/.gitignore b/.gitignore index 1da88e3f..144b93e8 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,6 @@ refinements/ # Large local research docs *.pdf + +# Never track saved ComfyUI workflow dumps — they carry whatever was typed in the graph. +movie_editor/backend/reference/*.workflow.json diff --git a/movie_editor/backend/builder.py b/movie_editor/backend/builder.py index af0f7a7a..7fb384a3 100644 --- a/movie_editor/backend/builder.py +++ b/movie_editor/backend/builder.py @@ -48,13 +48,6 @@ CORE: dict[str, str] = { "f2i": "LTXFloatToInt", } -# reference-workflow node id per core node (to seed widget values from the user's -# real settings). Missing -> bare object_info / param defaults. -REF_ID: dict[str, int] = { - "studio": 5299, "cond": 5147, "sampler": 5333, "concat": 4528, "separate": 4845, - "audiodec": 5021, "normaudio": 5314, "vhs": 5019, "saveref": 5328, "keyloader": 5303, - "pos": 5012, "neg": 5256, "frames": 4988, "fps": 4989, "f2i": 5145, -} # core internal links: core_id -> {input_name: (src_core_id, output_index)} CORE_LINKS: dict[str, dict[str, tuple[str, int]]] = { @@ -314,19 +307,25 @@ def _widget_choices(node_def: Optional[dict]) -> dict: return out -# ── reference workflow ──────────────────────────────────────────────────────── +# ── core widget baseline ────────────────────────────────────────────────────── def load_reference() -> dict: + """Baseline widget values for the fixed core nodes, keyed by core id — the tuned + settings (sigma schedules, sampler config, container format) the editor starts from + before per-run params are applied. + + Values only: no prompt text, and nothing user-typed. Every text field the run needs + arrives from the project at generate time, so none of it belongs in a checked-in file. + A missing/unreadable file just means each core node falls back to its object_info + defaults. + """ try: - return json.loads((config.REFERENCE_DIR / "montage.workflow.json").read_text()) + data = json.loads((config.REFERENCE_DIR / "core_widgets.json").read_text()) + return data if isinstance(data, dict) else {} except Exception: return {} -def _ref_widgets(ref: dict) -> dict[int, Any]: - return {n.get("id"): n.get("widgets_values") for n in (ref.get("nodes") or [])} - - # ── graph assembly ──────────────────────────────────────────────────────────── def _output_index(node_def: Optional[dict], out_name: str) -> int: @@ -352,7 +351,7 @@ def build(object_info: dict, models_config: dict, params: dict, media: dict | No # build (including the closures below), so every reference downstream is family-aware. family = family_of(models_config) CORE, CORE_LINKS, OPEN_PORTS = family_core(family) - ref_wv = _ref_widgets(load_reference()) + ref_wv = load_reference() graph: dict[str, dict] = {} # `blocking` is the subset of problems that should stop generation (required inputs). report: dict[str, list] = {"wired": [], "auto_wired": [], "ambiguous": [], "unsatisfied": [], "blocking": []} @@ -375,7 +374,7 @@ def build(object_info: dict, models_config: dict, params: dict, media: dict | No for cid, cls in CORE.items(): nd = object_info.get(cls) inputs = _widget_defaults(nd) - inputs.update(extract_widgets(nd, ref_wv.get(REF_ID.get(cid)))) + inputs.update(extract_widgets(nd, ref_wv.get(cid))) for inp, (src, idx) in CORE_LINKS.get(cid, {}).items(): inputs[inp] = [src, idx] protected_edges.add((cid, inp)) diff --git a/movie_editor/backend/reference/core_widgets.json b/movie_editor/backend/reference/core_widgets.json new file mode 100644 index 00000000..86414d74 --- /dev/null +++ b/movie_editor/backend/reference/core_widgets.json @@ -0,0 +1,74 @@ +{ + "cond": [ + 25 + ], + "f2i": [ + 0 + ], + "fps": [ + 30 + ], + "frames": [ + 241, + "fixed" + ], + "keyloader": [ + "tf", + "tf", + true, + "", + "", + "" + ], + "neg": [ + "underwater, diving, slow motion, still image, bad quality, subtitles, text, watermark, overlay, pc game, yelling, console game, childish, ugly, text, blur, logo, wordmark, static, low quality, noise, white noise, bleep, censoring, censor, bleeping, beep, beeping, newscast, interview, podcast, chinese, mutant, horror, 70's, film grain, comedy, stand-up " + ], + "normaudio": [ + -30 + ], + "pos": [ + "" + ], + "sampler": [ + 898993421681879, + "randomize", + 97, + 16, + 1, + 8, + false, + true, + false, + 0.25, + true, + 0.01, + 0, + 0, + 0.025, + 0.05, + "relative" + ], + "saveref": [ + "my_style_v1" + ], + "studio": [ + "Wrong details|loved", + "{\"refinement_key\":\"\",\"overrides\":{\"refinement_key\":false,\"feedback_prompt\":false,\"user_intent_prompt\":false,\"negative_prompt\":false},\"scene_builder\":{\"mode\":\"Pass-through\",\"scene\":\"-None-\",\"scene_name\":\"\",\"aliases\":\"\",\"scene_positive\":\"\",\"scene_negative\":\"\"},\"refiner\":{\"mode\":\"Refine\",\"advisor_mode\":\"Off\",\"advisor_thinking\":false,\"prompt_repair\":false,\"im_feeling_lucky\":false,\"reset_session\":false,\"feedback_prompt\":\"\",\"user_intent_prompt_override\":\"\",\"negative_prompt\":\"\",\"temporal_style\":\"accelerate\",\"split_by_transitions\":true,\"split_transition_placement\":\"start\",\"reference_injection\":false,\"vision_conditioning\":false,\"value_guidance\":true,\"steer_mode\":\"both\",\"absolute_strength\":0.3,\"latent_value_guidance\":true,\"transition_contrast\":1.5,\"batch_variants\":1,\"batch_mode\":\"Interactive Guessing\",\"batch_learning\":true,\"guess_mode\":false,\"guess_direction\":\"up\",\"guess_range\":1.2,\"guess_freeze_seed\":false},\"advisor_llm\":{\"enabled\":false,\"model_path\":\"huihui-ai/Huihui-Qwen3-8B-abliterated-v2\",\"dtype\":\"bfloat16\"},\"loras\":[],\"loras_config\":{\"mode\":\"ltx2\",\"per_block\":false},\"samplers\":{\"high\":{\"type\":\"Normalizing\",\"sigmas\":\"1.0, 0.99375, 0.9875, 0.98125, 0.975, 0.909375, 0.725, 0.421875, 0.0\",\"hybrid\":{\"eta\":1,\"eta_final\":1,\"normalize_strength\":1,\"normalize_start_sigma\":0.9,\"s_noise\":1,\"high_quality_pct\":0.5,\"correction_blend\":0.25,\"quality_sharpness\":0.3,\"motion_pulse_mode\":\"off\",\"motion_pulse_start_pct\":0.5,\"motion_pulse_count\":4,\"motion_pulse_spacing_pct\":0.22,\"motion_pulse_strength\":0.85,\"velocity_bias_mode\":\"capture\",\"velocity_bias_strength\":0.1,\"velocity_bias_source\":\"mean\",\"velocity_refinement_key\":\"\",\"rescue_mode\":false,\"rescue_threshold\":0.7,\"rescue_strength\":0.15,\"velocity_aspect_bucket\":\"any\",\"variability\":0},\"distilled\":{\"order\":1,\"final_correction_steps\":3,\"ab2_ramp\":false,\"normalize_strength\":0.5,\"normalize_start_sigma\":0.9,\"s_noise\":0,\"velocity_bias_mode\":\"capture\",\"velocity_bias_strength\":0.1,\"velocity_bias_source\":\"mean\",\"velocity_refinement_key\":\"\",\"rescue_mode\":false,\"rescue_threshold\":0.15,\"rescue_strength\":0.2},\"normalizing\":{\"normalize_strength\":0.5,\"normalize_start_sigma\":0.9,\"velocity_bias_mode\":\"capture_and_apply\",\"velocity_bias_strength\":0.1,\"velocity_bias_source\":\"mean\",\"velocity_refinement_key\":\"\",\"rescue_mode\":false,\"rescue_threshold\":0.15,\"rescue_strength\":0.2},\"ksampler_name\":\"ddim\"},\"low\":{\"type\":\"Distilled Flow\",\"sigmas\":\"0.909375, 0.725, 0.421875, 0.0\",\"hybrid\":{\"eta\":1,\"eta_final\":1,\"normalize_strength\":0,\"normalize_start_sigma\":0.9,\"s_noise\":1,\"high_quality_pct\":0.35,\"correction_blend\":1,\"quality_sharpness\":0,\"motion_pulse_mode\":\"off\",\"motion_pulse_start_pct\":0.3,\"motion_pulse_count\":2,\"motion_pulse_spacing_pct\":0.22,\"motion_pulse_strength\":0.85,\"velocity_bias_mode\":\"off\",\"velocity_bias_strength\":0,\"velocity_bias_source\":\"mean\",\"velocity_refinement_key\":\"default\",\"rescue_mode\":false,\"rescue_threshold\":0.15,\"rescue_strength\":0.2,\"velocity_aspect_bucket\":\"any\"},\"distilled\":{\"order\":1,\"final_correction_steps\":1,\"ab2_ramp\":false,\"normalize_strength\":0,\"normalize_start_sigma\":0.9,\"s_noise\":0,\"velocity_bias_mode\":\"off\",\"velocity_bias_strength\":0,\"velocity_bias_source\":\"mean\",\"velocity_refinement_key\":\"default\",\"rescue_mode\":false,\"rescue_threshold\":0.15,\"rescue_strength\":0.2},\"normalizing\":{\"normalize_strength\":0.5,\"normalize_start_sigma\":0.9,\"velocity_bias_mode\":\"off\",\"velocity_bias_strength\":0,\"velocity_bias_source\":\"mean\",\"velocity_refinement_key\":\"default\",\"rescue_mode\":false,\"rescue_threshold\":0.15,\"rescue_strength\":0.2},\"ksampler_name\":\"euler\"}}}", + "[]", + null, + "funpack_studio_open", + "funpack_studio_open", + "funpack_studio_batch" + ], + "vhs": { + "crf": 22, + "filename_prefix": "result_%date:MM-dd_hh-mm-ss%", + "format": "video/h265-mp4", + "frame_rate": 30, + "loop_count": 0, + "pingpong": false, + "pix_fmt": "yuv420p", + "save_metadata": false, + "save_output": false + } +} diff --git a/movie_editor/server.py b/movie_editor/server.py index f1f422e8..7e0b616e 100644 --- a/movie_editor/server.py +++ b/movie_editor/server.py @@ -1235,7 +1235,7 @@ if web is not None and PromptServer is not None: "ok": True, "comfy_url": config.comfy_base_url(), "template_exists": config.TEMPLATE_PATH.is_file(), - "reference_loaded": bool(builder.load_reference().get("nodes")), + "reference_loaded": bool(builder.load_reference()), "configured_slots": len(nodes.load_models().get("slots", [])), })