openchar-core 1.3.17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- inline_core/__init__.py +14 -0
- inline_core/characters/__init__.py +35 -0
- inline_core/characters/apply.py +311 -0
- inline_core/characters/charfile.py +270 -0
- inline_core/characters/encode.py +821 -0
- inline_core/characters/library.py +152 -0
- inline_core/characters/scoring.py +591 -0
- inline_core/characters/sweep.py +282 -0
- inline_core/characters/verify.py +194 -0
- inline_core/characters/weights.py +99 -0
- inline_core/components/__init__.py +1 -0
- inline_core/components/conditioning.py +20 -0
- inline_core/components/interfaces.py +91 -0
- inline_core/config.py +81 -0
- inline_core/device/__init__.py +1 -0
- inline_core/device/auto.py +35 -0
- inline_core/device/detect.py +199 -0
- inline_core/device/memory.py +374 -0
- inline_core/device/policy.py +171 -0
- inline_core/device/probe.py +76 -0
- inline_core/device/types.py +31 -0
- inline_core/errors.py +42 -0
- inline_core/extensions/__init__.py +10 -0
- inline_core/extensions/api.py +214 -0
- inline_core/extensions/constraints.py +180 -0
- inline_core/extensions/fetch.py +196 -0
- inline_core/extensions/handlers.py +144 -0
- inline_core/extensions/importer.py +91 -0
- inline_core/extensions/install.py +669 -0
- inline_core/extensions/loader.py +285 -0
- inline_core/extensions/manifest.py +361 -0
- inline_core/extensions/models.py +58 -0
- inline_core/extensions/paths.py +235 -0
- inline_core/extensions/resolve.py +298 -0
- inline_core/extensions/scanner.py +448 -0
- inline_core/extensions/state.py +168 -0
- inline_core/extensions/tools.py +138 -0
- inline_core/ffmpeg.py +30 -0
- inline_core/graph/__init__.py +1 -0
- inline_core/graph/cache.py +138 -0
- inline_core/graph/descriptor.py +88 -0
- inline_core/graph/executor.py +114 -0
- inline_core/graph/loader_runners.py +131 -0
- inline_core/graph/primitives.py +160 -0
- inline_core/graph/registry.py +97 -0
- inline_core/graph/runners.py +90 -0
- inline_core/graph/schema.py +161 -0
- inline_core/graph/topo.py +49 -0
- inline_core/graph/validate.py +56 -0
- inline_core/media.py +11 -0
- inline_core/models/__init__.py +8 -0
- inline_core/models/catalog.py +260 -0
- inline_core/models/character/__init__.py +9 -0
- inline_core/models/character/finetune.py +202 -0
- inline_core/models/character/runner.py +807 -0
- inline_core/models/characterreqs.py +85 -0
- inline_core/models/checkpoint.py +129 -0
- inline_core/models/controlspace.py +26 -0
- inline_core/models/flux2/__init__.py +1 -0
- inline_core/models/flux2/controlnet.py +234 -0
- inline_core/models/flux2/embeds.py +165 -0
- inline_core/models/flux2/provider.py +84 -0
- inline_core/models/flux2/requirements.py +449 -0
- inline_core/models/flux2/runner.py +760 -0
- inline_core/models/flux2/variants.py +398 -0
- inline_core/models/keymap.py +319 -0
- inline_core/models/krea2/__init__.py +9 -0
- inline_core/models/krea2/convert.py +138 -0
- inline_core/models/krea2/depth_control.py +149 -0
- inline_core/models/krea2/img2img.py +89 -0
- inline_core/models/krea2/provider.py +68 -0
- inline_core/models/krea2/requirements.py +254 -0
- inline_core/models/krea2/runner.py +507 -0
- inline_core/models/loaders.py +1444 -0
- inline_core/models/lora.py +232 -0
- inline_core/models/ltx25/__init__.py +18 -0
- inline_core/models/ltx25/lora_keys.py +73 -0
- inline_core/models/ltx25/memory.py +201 -0
- inline_core/models/ltx25/pipeline.py +373 -0
- inline_core/models/ltx25/provider.py +113 -0
- inline_core/models/ltx25/requirements.py +462 -0
- inline_core/models/ltx25/runner.py +400 -0
- inline_core/models/ltx25/vendor/__init__.py +38 -0
- inline_core/models/ltx25/vendor/ltx_core/__init__.py +0 -0
- inline_core/models/ltx25/vendor/ltx_core/allocator_trim_strategy.py +8 -0
- inline_core/models/ltx25/vendor/ltx_core/batch_split.py +105 -0
- inline_core/models/ltx25/vendor/ltx_core/block_streaming/__init__.py +20 -0
- inline_core/models/ltx25/vendor/ltx_core/block_streaming/block_fetcher.py +83 -0
- inline_core/models/ltx25/vendor/ltx_core/block_streaming/builder.py +500 -0
- inline_core/models/ltx25/vendor/ltx_core/block_streaming/disk.py +167 -0
- inline_core/models/ltx25/vendor/ltx_core/block_streaming/pool.py +71 -0
- inline_core/models/ltx25/vendor/ltx_core/block_streaming/provider.py +165 -0
- inline_core/models/ltx25/vendor/ltx_core/block_streaming/source.py +175 -0
- inline_core/models/ltx25/vendor/ltx_core/block_streaming/stream_sync.py +174 -0
- inline_core/models/ltx25/vendor/ltx_core/block_streaming/utils.py +169 -0
- inline_core/models/ltx25/vendor/ltx_core/block_streaming/wrapper.py +101 -0
- inline_core/models/ltx25/vendor/ltx_core/color/__init__.py +59 -0
- inline_core/models/ltx25/vendor/ltx_core/color/audio_mux.py +82 -0
- inline_core/models/ltx25/vendor/ltx_core/color/hlg.py +306 -0
- inline_core/models/ltx25/vendor/ltx_core/color/primaries.py +125 -0
- inline_core/models/ltx25/vendor/ltx_core/color/yuv.py +228 -0
- inline_core/models/ltx25/vendor/ltx_core/components/__init__.py +10 -0
- inline_core/models/ltx25/vendor/ltx_core/components/diffusion_steps.py +252 -0
- inline_core/models/ltx25/vendor/ltx_core/components/guiders.py +369 -0
- inline_core/models/ltx25/vendor/ltx_core/components/noisers.py +37 -0
- inline_core/models/ltx25/vendor/ltx_core/components/patchifiers.py +353 -0
- inline_core/models/ltx25/vendor/ltx_core/components/protocols.py +101 -0
- inline_core/models/ltx25/vendor/ltx_core/components/schedulers.py +130 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/__init__.py +25 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/exceptions.py +4 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/item.py +20 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/mask_utils.py +244 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/types/__init__.py +19 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/types/attention_strength_wrapper.py +71 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/types/keyframe_cond.py +90 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/types/keyframe_slots.py +174 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/types/latent_cond.py +43 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/types/mask_cond.py +49 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/types/noise_mask_cond.py +45 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/types/reference_audio_cond.py +65 -0
- inline_core/models/ltx25/vendor/ltx_core/conditioning/types/reference_video_cond.py +108 -0
- inline_core/models/ltx25/vendor/ltx_core/devices.py +112 -0
- inline_core/models/ltx25/vendor/ltx_core/duration_head/__init__.py +11 -0
- inline_core/models/ltx25/vendor/ltx_core/duration_head/duration_head.py +118 -0
- inline_core/models/ltx25/vendor/ltx_core/duration_head/model_configurator.py +35 -0
- inline_core/models/ltx25/vendor/ltx_core/guidance/__init__.py +15 -0
- inline_core/models/ltx25/vendor/ltx_core/guidance/perturbations.py +143 -0
- inline_core/models/ltx25/vendor/ltx_core/hdr.py +172 -0
- inline_core/models/ltx25/vendor/ltx_core/loader/__init__.py +66 -0
- inline_core/models/ltx25/vendor/ltx_core/loader/attention_ops.py +42 -0
- inline_core/models/ltx25/vendor/ltx_core/loader/fuse_loras.py +204 -0
- inline_core/models/ltx25/vendor/ltx_core/loader/helpers.py +95 -0
- inline_core/models/ltx25/vendor/ltx_core/loader/kernels.py +79 -0
- inline_core/models/ltx25/vendor/ltx_core/loader/module_ops.py +14 -0
- inline_core/models/ltx25/vendor/ltx_core/loader/primitives.py +176 -0
- inline_core/models/ltx25/vendor/ltx_core/loader/registry.py +147 -0
- inline_core/models/ltx25/vendor/ltx_core/loader/sd_ops.py +145 -0
- inline_core/models/ltx25/vendor/ltx_core/loader/sft_loader.py +77 -0
- inline_core/models/ltx25/vendor/ltx_core/loader/single_gpu_model_builder.py +294 -0
- inline_core/models/ltx25/vendor/ltx_core/modality_tiling.py +243 -0
- inline_core/models/ltx25/vendor/ltx_core/model/__init__.py +11 -0
- inline_core/models/ltx25/vendor/ltx_core/model/audio_vae/__init__.py +29 -0
- inline_core/models/ltx25/vendor/ltx_core/model/audio_vae/attention.py +71 -0
- inline_core/models/ltx25/vendor/ltx_core/model/audio_vae/audio_vae.py +509 -0
- inline_core/models/ltx25/vendor/ltx_core/model/audio_vae/causal_conv_2d.py +110 -0
- inline_core/models/ltx25/vendor/ltx_core/model/audio_vae/causality_axis.py +10 -0
- inline_core/models/ltx25/vendor/ltx_core/model/audio_vae/downsample.py +110 -0
- inline_core/models/ltx25/vendor/ltx_core/model/audio_vae/model_configurator.py +200 -0
- inline_core/models/ltx25/vendor/ltx_core/model/audio_vae/ops.py +75 -0
- inline_core/models/ltx25/vendor/ltx_core/model/audio_vae/resnet.py +176 -0
- inline_core/models/ltx25/vendor/ltx_core/model/audio_vae/upsample.py +106 -0
- inline_core/models/ltx25/vendor/ltx_core/model/audio_vae/vocoder.py +630 -0
- inline_core/models/ltx25/vendor/ltx_core/model/common/__init__.py +9 -0
- inline_core/models/ltx25/vendor/ltx_core/model/common/normalization.py +59 -0
- inline_core/models/ltx25/vendor/ltx_core/model/disposable.py +42 -0
- inline_core/models/ltx25/vendor/ltx_core/model/model_protocol.py +44 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/__init__.py +22 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/adaln.py +45 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/attention.py +579 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/compiling.py +239 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/cudagraph_capture.py +167 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/feed_forward.py +15 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/gelu_approx.py +10 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/modality.py +77 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/model.py +604 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/model_configurator.py +239 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/ops.py +106 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/rope.py +224 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/text_projection.py +38 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/timestep_embedding.py +143 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/transformer.py +447 -0
- inline_core/models/ltx25/vendor/ltx_core/model/transformer/transformer_args.py +411 -0
- inline_core/models/ltx25/vendor/ltx_core/model/upsampler/__init__.py +10 -0
- inline_core/models/ltx25/vendor/ltx_core/model/upsampler/blur_downsample.py +53 -0
- inline_core/models/ltx25/vendor/ltx_core/model/upsampler/model.py +143 -0
- inline_core/models/ltx25/vendor/ltx_core/model/upsampler/model_configurator.py +31 -0
- inline_core/models/ltx25/vendor/ltx_core/model/upsampler/pixel_shuffle.py +54 -0
- inline_core/models/ltx25/vendor/ltx_core/model/upsampler/res_block.py +37 -0
- inline_core/models/ltx25/vendor/ltx_core/model/upsampler/spatial_rational_resampler.py +47 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/__init__.py +54 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/attention.py +69 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/conv_video_decoder.py +557 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/convolution.py +317 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/diffusion_tiling.py +759 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/diffusion_video_decoder.py +748 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/enums.py +20 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/memory_efficient_decode.py +683 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/model_configurator.py +565 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/normalization.py +3 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/ops.py +84 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/resnet.py +277 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/sampling.py +123 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/__init__.py +107 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/apply.py +206 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/attention.py +174 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/blocks.py +83 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/chunked/__init__.py +1 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/chunked/attn.py +420 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/chunked/block.py +70 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/chunked/context.py +88 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/chunked/mlp.py +178 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/combined/__init__.py +1 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/combined/attn.py +123 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/combined/block.py +32 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/combined/context.py +21 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/combined/mlp.py +24 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/compiling.py +95 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/config.py +110 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/det_attn_rope.py +175 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/dsl_kernels/__init__.py +39 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/dsl_kernels/apply_dsl.py +111 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/dsl_kernels/attn.py +88 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/dsl_kernels/block.py +151 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/dsl_kernels/chain.py +79 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/fallback_na/__init__.py +154 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/fallback_na/eager.py +166 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/fallback_na/triton_na.py +181 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/layers.py +86 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/qkv.py +34 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/rope.py +109 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/rope_math.py +61 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/transformer/swiglu.py +479 -0
- inline_core/models/ltx25/vendor/ltx_core/model/video_vae/video_vae.py +637 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/__init__.py +11 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/gemma/__init__.py +6 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/gemma/accelerate_wrapper.py +23 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/gemma/batch_parallel_wrapper.py +71 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/gemma/broadcast_wrapper.py +120 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/gemma/loader.py +120 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/sharded_sd.py +191 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/transformer/__init__.py +13 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/transformer/attention.py +270 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/transformer/sequence_parallel.py +318 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/transformer/tiled_data_parallel.py +99 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/vae/__init__.py +5 -0
- inline_core/models/ltx25/vendor/ltx_core/multigpu/vae/distributed_decoder.py +318 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/__init__.py +17 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/blockwise/__init__.py +47 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/blockwise/_impl.py +433 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/fp8_cast.py +337 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/fp8_scaled_mm.py +203 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/nvfp4/__init__.py +64 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/nvfp4/convert.py +126 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/nvfp4/fuse.py +50 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/nvfp4/linear.py +139 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/nvfp4/prequant.py +207 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/nvfp4/types.py +13 -0
- inline_core/models/ltx25/vendor/ltx_core/quantization/policy.py +24 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/__init__.py +1 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/__init__.py +49 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/embeddings_connector.py +256 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/embeddings_processor.py +117 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/encoders/base_encoder.py +262 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/encoders/encoder_configurator.py +481 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/encoders/prompts/gemma3_i2v_system_prompt.txt +30 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/encoders/prompts/gemma3_t2v_system_prompt.txt +40 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/encoders/prompts/gemma4_i2v_system_prompt.txt +27 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/encoders/prompts/gemma4_t2v_system_prompt.txt +25 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/feature_extractor.py +129 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/gemma_assets.py +396 -0
- inline_core/models/ltx25/vendor/ltx_core/text_encoders/gemma/tokenizer.py +63 -0
- inline_core/models/ltx25/vendor/ltx_core/tiling.py +945 -0
- inline_core/models/ltx25/vendor/ltx_core/tools.py +280 -0
- inline_core/models/ltx25/vendor/ltx_core/types.py +300 -0
- inline_core/models/ltx25/vendor/ltx_core/utils.py +62 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/__init__.py +74 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/a2vid_two_stage.py +387 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/dfr_layout.py +213 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/dfr_pipeline.py +630 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/distilled.py +367 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/distilled_mgpu.py +233 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/dubit.py +401 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/hdr_ic_lora.py +987 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/ic_lora.py +540 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/iclora_utils.py +170 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/keyframe_interpolation.py +362 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/__init__.py +31 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/_broadcast.py +68 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/bp_gemma_builder.py +90 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/controller.py +382 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/delegating_builder.py +91 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/fleet.py +375 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/gemma_builders.py +199 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/nccl_groups.py +31 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/runner.py +62 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/sp_builder.py +62 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/tdp_builder.py +75 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/vae_builders.py +87 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/multigpu/weight_tracker.py +181 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/retake.py +397 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/t2a_one_stage.py +215 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/ti2vid_one_stage.py +311 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/ti2vid_two_stages.py +380 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/ti2vid_two_stages_hq.py +409 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/ti2vid_two_stages_hq_mgpu.py +278 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/ti2vid_two_stages_mgpu.py +279 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/__init__.py +57 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/args.py +1214 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/blocks.py +1236 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/constants.py +199 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/denoisers.py +361 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/gpu_model.py +34 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/helpers.py +585 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/media_io/__init__.py +76 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/media_io/color_config.py +103 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/media_io/decode.py +435 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/media_io/encode.py +302 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/media_io/exr.py +255 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/media_io/range_map.py +25 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/media_io/resize.py +143 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/model_paths.py +189 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/quantization_factory.py +50 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/res2s.py +62 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/samplers.py +707 -0
- inline_core/models/ltx25/vendor/ltx_pipelines/utils/types.py +144 -0
- inline_core/models/minimaxh3/__init__.py +7 -0
- inline_core/models/minimaxh3/adaln.py +248 -0
- inline_core/models/minimaxh3/keys.py +158 -0
- inline_core/models/minimaxh3/load.py +448 -0
- inline_core/models/minimaxh3/lora_keys.py +235 -0
- inline_core/models/minimaxh3/nvfp4.py +201 -0
- inline_core/models/minimaxh3/pipeline.py +1083 -0
- inline_core/models/minimaxh3/provider.py +102 -0
- inline_core/models/minimaxh3/requirements.py +450 -0
- inline_core/models/minimaxh3/runner.py +622 -0
- inline_core/models/minimaxh3/vae_keys.py +125 -0
- inline_core/models/minimaxh3/vendor/__init__.py +46 -0
- inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3.py +922 -0
- inline_core/models/minimaxh3/vendor/autoencoder_kl_minimax_h3_audio.py +679 -0
- inline_core/models/minimaxh3/vendor/before_denoise.py +425 -0
- inline_core/models/minimaxh3/vendor/before_encoder.py +408 -0
- inline_core/models/minimaxh3/vendor/decoders.py +199 -0
- inline_core/models/minimaxh3/vendor/denoise.py +325 -0
- inline_core/models/minimaxh3/vendor/encoders.py +639 -0
- inline_core/models/minimaxh3/vendor/modular_blocks_minimax_h3.py +345 -0
- inline_core/models/minimaxh3/vendor/modular_pipeline.py +115 -0
- inline_core/models/minimaxh3/vendor/packing.py +538 -0
- inline_core/models/minimaxh3/vendor/packing_ref2va.py +841 -0
- inline_core/models/minimaxh3/vendor/scheduling_minimax_h3.py +288 -0
- inline_core/models/minimaxh3/vendor/transformer_minimax_h3.py +644 -0
- inline_core/models/offload.py +285 -0
- inline_core/models/pipeline_runtime.py +832 -0
- inline_core/models/prepared.py +151 -0
- inline_core/models/preprocess/__init__.py +5 -0
- inline_core/models/preprocess/requirements.py +59 -0
- inline_core/models/preprocess/runner.py +174 -0
- inline_core/models/references.py +125 -0
- inline_core/models/registry_index.py +301 -0
- inline_core/models/requirements.py +145 -0
- inline_core/models/sampling.py +174 -0
- inline_core/models/training/__init__.py +3 -0
- inline_core/models/training/runner.py +239 -0
- inline_core/models/trainingreqs.py +109 -0
- inline_core/models/video_params.py +147 -0
- inline_core/models/zimage/__init__.py +11 -0
- inline_core/models/zimage/primitives.py +578 -0
- inline_core/models/zimage/provider.py +73 -0
- inline_core/models/zimage/requirements.py +348 -0
- inline_core/models/zimage/runner.py +529 -0
- inline_core/parallel/__init__.py +13 -0
- inline_core/parallel/config.py +50 -0
- inline_core/parallel/group.py +136 -0
- inline_core/parallel/launch.py +44 -0
- inline_core/parallel/protocol.py +53 -0
- inline_core/parallel/registry.py +31 -0
- inline_core/parallel/worker.py +75 -0
- inline_core/runtime/__init__.py +1 -0
- inline_core/runtime/context.py +38 -0
- inline_core/runtime/file_store.py +81 -0
- inline_core/runtime/progress.py +93 -0
- inline_core/runtime/run.py +116 -0
- inline_core/runtime/store.py +45 -0
- inline_core/runtime/video_encode.py +211 -0
- inline_core/sampling/__init__.py +1 -0
- inline_core/sampling/batch.py +116 -0
- inline_core/server/__init__.py +1 -0
- inline_core/server/__main__.py +118 -0
- inline_core/server/app.py +689 -0
- inline_core/server/assets.py +47 -0
- inline_core/server/bootstrap.py +160 -0
- inline_core/server/frontend.py +37 -0
- inline_core/server/manager.py +276 -0
- inline_core/server/rpc.py +75 -0
- inline_core/server/run_store.py +155 -0
- inline_core/server/serialize.py +223 -0
- inline_core/server/version.py +205 -0
- inline_core/studio/__init__.py +7 -0
- inline_core/studio/activity.py +396 -0
- inline_core/studio/assets.py +194 -0
- inline_core/studio/characters.py +474 -0
- inline_core/studio/config.py +28 -0
- inline_core/studio/dataset_import.py +236 -0
- inline_core/studio/fal.py +564 -0
- inline_core/studio/finetune.py +632 -0
- inline_core/studio/frames.py +565 -0
- inline_core/studio/generation.py +342 -0
- inline_core/studio/graph_build.py +402 -0
- inline_core/studio/handlers.py +420 -0
- inline_core/studio/image_meta.py +36 -0
- inline_core/studio/models.py +494 -0
- inline_core/studio/moodboard.py +576 -0
- inline_core/studio/peaks.py +170 -0
- inline_core/studio/recipe.py +282 -0
- inline_core/studio/schema.py +436 -0
- inline_core/studio/store.py +445 -0
- inline_core/studio/system_stats.py +92 -0
- inline_core/studio/timeline/__init__.py +6 -0
- inline_core/studio/timeline/compose.py +130 -0
- inline_core/studio/timeline/ffmpeg.py +76 -0
- inline_core/studio/timeline/render.py +120 -0
- inline_core/studio/timeline/resolve.py +197 -0
- inline_core/studio/training.py +934 -0
- inline_core/studio/training_store.py +358 -0
- inline_core/studio/tuning_report.py +225 -0
- inline_core/studio/workflows.py +193 -0
- inline_core/takes.py +31 -0
- inline_core/training/__init__.py +10 -0
- inline_core/training/__main__.py +74 -0
- inline_core/training/arch.py +518 -0
- inline_core/training/cache.py +116 -0
- inline_core/training/caption.py +177 -0
- inline_core/training/dataset.py +312 -0
- inline_core/training/h3.py +529 -0
- inline_core/training/ltx25.py +364 -0
- inline_core/training/models.py +618 -0
- inline_core/training/precache_store.py +138 -0
- inline_core/training/protocol.py +61 -0
- inline_core/training/trainer.py +395 -0
- openchar_core-1.3.17.dist-info/METADATA +298 -0
- openchar_core-1.3.17.dist-info/RECORD +432 -0
- openchar_core-1.3.17.dist-info/WHEEL +4 -0
- openchar_core-1.3.17.dist-info/entry_points.txt +3 -0
inline_core/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Inline Core: the generation engine behind Inline.
|
|
2
|
+
|
|
3
|
+
Takes a typed node graph and returns immutable takes. See PLAN.md for the architecture and
|
|
4
|
+
docs/contract.md for the Storyline API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
#: Resolved from the installed package, so pyproject.toml stays the only place a release is
|
|
11
|
+
#: bumped. An editable install records this at install time; reinstall after bumping.
|
|
12
|
+
__version__ = version("openchar-core")
|
|
13
|
+
except PackageNotFoundError: # a source tree that was never installed
|
|
14
|
+
__version__ = "0.0.0"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Persistent characters: the ``.char`` container, its compiled payloads, and identity scoring."""
|
|
2
|
+
|
|
3
|
+
from .charfile import (
|
|
4
|
+
FORMAT_VERSION,
|
|
5
|
+
MAGIC,
|
|
6
|
+
SIGNATURE,
|
|
7
|
+
CharDoc,
|
|
8
|
+
CharFileError,
|
|
9
|
+
Manifest,
|
|
10
|
+
centroid_valid,
|
|
11
|
+
looks_like_char,
|
|
12
|
+
member_name,
|
|
13
|
+
payload_valid,
|
|
14
|
+
read,
|
|
15
|
+
refs_fingerprint,
|
|
16
|
+
sha256_bytes,
|
|
17
|
+
write,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"FORMAT_VERSION",
|
|
22
|
+
"MAGIC",
|
|
23
|
+
"SIGNATURE",
|
|
24
|
+
"CharDoc",
|
|
25
|
+
"CharFileError",
|
|
26
|
+
"Manifest",
|
|
27
|
+
"centroid_valid",
|
|
28
|
+
"looks_like_char",
|
|
29
|
+
"member_name",
|
|
30
|
+
"payload_valid",
|
|
31
|
+
"read",
|
|
32
|
+
"refs_fingerprint",
|
|
33
|
+
"sha256_bytes",
|
|
34
|
+
"write",
|
|
35
|
+
]
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""``char_apply``: a character turned into the references and prompt text a run needs.
|
|
2
|
+
|
|
3
|
+
Payload PNGs are extracted from the zip once, keyed by content hash so an edit lands in a new
|
|
4
|
+
directory rather than mutating one a running graph is reading.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import shutil
|
|
11
|
+
from collections.abc import Sequence
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from ..config import data_dir
|
|
16
|
+
from ..takes import AssetRef
|
|
17
|
+
from . import charfile as cf
|
|
18
|
+
from . import encode, library
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger("inline_core.characters")
|
|
21
|
+
|
|
22
|
+
#: Extracted payload bytes to keep. Tiny next to a checkpoint, so this holds many characters.
|
|
23
|
+
_CACHE_LIMIT_BYTES = 2 * 1024**3
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AppliedCharacter:
|
|
27
|
+
"""What a runner needs: ordered reference handles, plus the prompt text that names them."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
name: str,
|
|
32
|
+
refs: list[AssetRef],
|
|
33
|
+
description: str,
|
|
34
|
+
lora: Path | None = None,
|
|
35
|
+
lora_strength: float = 1.0,
|
|
36
|
+
roles: list[str] | None = None,
|
|
37
|
+
) -> None:
|
|
38
|
+
self.name = name
|
|
39
|
+
self.refs = refs
|
|
40
|
+
self.description = description
|
|
41
|
+
#: One role per ref, in the same order. All face when a character predates roles, which is
|
|
42
|
+
#: what keeps an old character's prompt byte-identical to what it used to produce.
|
|
43
|
+
self.roles = roles or [cf.ROLE_FACE] * len(refs)
|
|
44
|
+
#: A trained adapter, which for a model with no reference channel is the only route.
|
|
45
|
+
self.lora = lora
|
|
46
|
+
#: What it fuses at. Set on Attach Adapter, because an overfit adapter is only usable
|
|
47
|
+
#: turned down and the character wire carries no controls of its own.
|
|
48
|
+
self.lora_strength = lora_strength
|
|
49
|
+
|
|
50
|
+
def _role_lines(self, first_position: int, style: str) -> str:
|
|
51
|
+
"""Sentences binding each role to the positions it actually landed on.
|
|
52
|
+
|
|
53
|
+
Written from the allocation rather than alongside it, so the numbers cannot drift from the
|
|
54
|
+
references. Silent when everything is face: an old character keeps its exact prompt.
|
|
55
|
+
"""
|
|
56
|
+
grouped: dict[str, list[int]] = {}
|
|
57
|
+
for offset, role in enumerate(self.roles[: len(self.refs)]):
|
|
58
|
+
grouped.setdefault(role, []).append(first_position + offset)
|
|
59
|
+
if set(grouped) <= {cf.ROLE_FACE}:
|
|
60
|
+
return ""
|
|
61
|
+
out = ""
|
|
62
|
+
for role in cf.ROLES:
|
|
63
|
+
numbers = grouped.get(role)
|
|
64
|
+
if numbers:
|
|
65
|
+
verb = "shows" if len(numbers) == 1 else "show"
|
|
66
|
+
out += f" {_positions(style, numbers)} {verb} {self.name}'s {_ROLE_BINDINGS[role]}."
|
|
67
|
+
return out
|
|
68
|
+
|
|
69
|
+
def prompt_prefix(
|
|
70
|
+
self, first_position: int, style: str = "ordinal", role_lines: bool = False
|
|
71
|
+
) -> str:
|
|
72
|
+
"""Text naming the positions the character lands on, so positional prompting resolves.
|
|
73
|
+
|
|
74
|
+
``style`` because a model only resolves the form it was trained on: FLUX.2 reads the ordinal
|
|
75
|
+
prose below, MiniMax H3 reads ``<Picture N>`` tokens (``models/references.py``), Seedance
|
|
76
|
+
2.0 reads ``@ImageN``, and handing any of them another names positions it cannot see.
|
|
77
|
+
|
|
78
|
+
``role_lines`` defaults off: the bindings are unvalidated, see docs/characters.md.
|
|
79
|
+
"""
|
|
80
|
+
if not self.refs:
|
|
81
|
+
# A LoRA carries the likeness, so the description is all the prompt needs.
|
|
82
|
+
detail = " ".join(self.description.split())
|
|
83
|
+
return f"{detail} " if detail else ""
|
|
84
|
+
positions = [first_position + i for i in range(len(self.refs))]
|
|
85
|
+
if style == "token":
|
|
86
|
+
tokens = " ".join(f"<Picture {n}>" for n in positions)
|
|
87
|
+
plural = "" if len(positions) == 1 else "each"
|
|
88
|
+
which = f"{tokens} {'shows' if not plural else 'show'}"
|
|
89
|
+
elif style == "at-image":
|
|
90
|
+
# Seedance's own addressing syntax, so each position is declared exactly once here.
|
|
91
|
+
tokens = [f"@Image{n}" for n in positions]
|
|
92
|
+
if len(tokens) == 1:
|
|
93
|
+
which = f"{tokens[0]} shows"
|
|
94
|
+
else:
|
|
95
|
+
which = f"{', '.join(tokens[:-1])} and {tokens[-1]} show"
|
|
96
|
+
elif len(positions) == 1:
|
|
97
|
+
which = f"Image {positions[0]} shows"
|
|
98
|
+
else:
|
|
99
|
+
ordinals = [str(n) for n in positions]
|
|
100
|
+
which = f"Images {', '.join(ordinals[:-1])} and {ordinals[-1]} show"
|
|
101
|
+
line = f"{which} {self.name}, the same character in every image."
|
|
102
|
+
if role_lines:
|
|
103
|
+
line += self._role_lines(first_position, style)
|
|
104
|
+
detail = " ".join(self.description.split())
|
|
105
|
+
if not detail:
|
|
106
|
+
return f"{line} "
|
|
107
|
+
# Prose, not comma tags: without this it runs into the user's prompt unpunctuated.
|
|
108
|
+
if detail[-1] not in ".!?":
|
|
109
|
+
detail += "."
|
|
110
|
+
return f"{line} {detail} "
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _positions(style: str, numbers: list[int]) -> str:
|
|
114
|
+
"""How a role line refers *back* to positions, in prose, never re-declaring them."""
|
|
115
|
+
# Never `<Picture N>` or `@ImageN`: a reserved label repeated replayed the references on H3, and
|
|
116
|
+
# nothing has established that Seedance treats a second `@ImageN` as a reference either.
|
|
117
|
+
noun = "Picture" if style == "token" else "Image"
|
|
118
|
+
if len(numbers) == 1:
|
|
119
|
+
return f"{noun} {numbers[0]}"
|
|
120
|
+
return f"{noun}s {', '.join(str(n) for n in numbers[:-1])} and {numbers[-1]}"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
#: What each role binds to. Naming only, never describing: "slim build" or "red jacket" would be
|
|
124
|
+
#: text competing with the reference images, which is the caption-overrides-identity failure the
|
|
125
|
+
#: LoRA evals already found.
|
|
126
|
+
_ROLE_BINDINGS = {
|
|
127
|
+
cf.ROLE_FACE: "face",
|
|
128
|
+
cf.ROLE_BODY: "full body and build",
|
|
129
|
+
cf.ROLE_CLOTH: "outfit",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _cache_root() -> Path:
|
|
134
|
+
return data_dir() / "characters"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def char_apply(
|
|
138
|
+
chosen: str,
|
|
139
|
+
arch: str = encode.FLUX2_KLEIN_ARCH,
|
|
140
|
+
prefer: str | None = None,
|
|
141
|
+
limit: int | None = None,
|
|
142
|
+
keep_roles: tuple[str, ...] | None = None,
|
|
143
|
+
select: Sequence[int] | None = None,
|
|
144
|
+
) -> AppliedCharacter | None:
|
|
145
|
+
"""How a character applies on ``arch``, or None when none is picked. An unreadable pick raises
|
|
146
|
+
rather than silently generating the wrong person.
|
|
147
|
+
|
|
148
|
+
``arch`` matters because a model without a reference channel can only take the adapter, and its
|
|
149
|
+
payloads are keyed separately. ``keep_roles`` narrows which of them are sent at all, per render,
|
|
150
|
+
so testing a character face-only does not mean writing a second character. ``select`` names
|
|
151
|
+
individual references by position, which is what lets a sweep measure one reference's worth."""
|
|
152
|
+
name = str(chosen or "").strip()
|
|
153
|
+
if not name:
|
|
154
|
+
return None
|
|
155
|
+
path = library.resolve(name)
|
|
156
|
+
if path is None:
|
|
157
|
+
raise FileNotFoundError(
|
|
158
|
+
f"Character {name!r} is not in models/characters/. Pick another in the node's settings."
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
doc = cf.read(path)
|
|
162
|
+
digest = library.content_hash(path)
|
|
163
|
+
# Whether this arch reads references at all, which is exactly the archs with a policy for them.
|
|
164
|
+
references = arch in encode.REFERENCE_POLICIES
|
|
165
|
+
|
|
166
|
+
if references and not cf.payload_valid(doc.manifest, arch, encode.PAYLOAD_ENCODER_VERSION):
|
|
167
|
+
doc = _recompile(doc, path, arch)
|
|
168
|
+
digest = library.content_hash(path)
|
|
169
|
+
|
|
170
|
+
description = _description(doc)
|
|
171
|
+
lora = _extract_lora(doc, digest, arch)
|
|
172
|
+
strength = encode.lora_strength(doc.manifest, arch)
|
|
173
|
+
# A trained adapter wins unless the character says otherwise: the user asked for it explicitly,
|
|
174
|
+
# and loading both would apply the identity twice. `prefer` overrides both, for a node that can
|
|
175
|
+
# only run one way: H3's reference partition needs a reference and cannot use an adapter alone.
|
|
176
|
+
mode = prefer or doc.manifest.apply.get(arch) or ("lora" if lora else "reference")
|
|
177
|
+
# No reference channel on this arch, so the adapter is the only way it can apply at all.
|
|
178
|
+
if not references:
|
|
179
|
+
mode = "lora"
|
|
180
|
+
refs, roles = ([], []) if mode == "lora" else _extract(doc, digest, arch)
|
|
181
|
+
# Before the role filter and the cap, so both still divide over what was actually selected.
|
|
182
|
+
if select is not None:
|
|
183
|
+
chosen_indices = sorted({i for i in select if 0 <= i < len(refs)})
|
|
184
|
+
refs, roles = [refs[i] for i in chosen_indices], [roles[i] for i in chosen_indices]
|
|
185
|
+
if keep_roles is not None:
|
|
186
|
+
kept = [i for i, role in enumerate(roles) if role in keep_roles]
|
|
187
|
+
refs, roles = [refs[i] for i in kept], [roles[i] for i in kept]
|
|
188
|
+
if limit is not None:
|
|
189
|
+
refs, roles = _fit_roles(refs, roles, limit)
|
|
190
|
+
return AppliedCharacter(
|
|
191
|
+
doc.manifest.name or path.stem,
|
|
192
|
+
refs,
|
|
193
|
+
description,
|
|
194
|
+
lora if mode == "lora" else None,
|
|
195
|
+
strength,
|
|
196
|
+
roles=roles,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _fit_roles(
|
|
201
|
+
refs: list[AssetRef], roles: list[str], limit: int
|
|
202
|
+
) -> tuple[list[AssetRef], list[str]]:
|
|
203
|
+
"""Cut to what a model takes, dividing the slots by role rather than by arrival.
|
|
204
|
+
|
|
205
|
+
Trimming the tail instead would drop whichever role happens to be last, so a character with
|
|
206
|
+
face, body and cloth would silently lose its wardrobe on any model that takes fewer than it
|
|
207
|
+
holds. Order is preserved, because order is what the prompt numbers.
|
|
208
|
+
"""
|
|
209
|
+
if limit >= len(refs) or limit <= 0:
|
|
210
|
+
return refs[: max(0, limit)], roles[: max(0, limit)]
|
|
211
|
+
counts = {role: roles.count(role) for role in cf.ROLES}
|
|
212
|
+
share = encode.allocate_roles(counts, limit)
|
|
213
|
+
keep: list[int] = []
|
|
214
|
+
taken = dict.fromkeys(cf.ROLES, 0)
|
|
215
|
+
for index, role in enumerate(roles):
|
|
216
|
+
if taken[role] < share.get(role, 0):
|
|
217
|
+
taken[role] += 1
|
|
218
|
+
keep.append(index)
|
|
219
|
+
return [refs[i] for i in keep], [roles[i] for i in keep]
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _extract_lora(doc: cf.CharDoc, digest: str, arch: str) -> Path | None:
|
|
223
|
+
"""The adapter for ``arch``, or None. A stale one is the wrong face, so it is ignored."""
|
|
224
|
+
entry = encode.lora_payload(doc.manifest, arch)
|
|
225
|
+
if not entry:
|
|
226
|
+
return None
|
|
227
|
+
key = encode.payload_key(arch, encode.PAYLOAD_LORA)
|
|
228
|
+
if not cf.payload_valid(doc.manifest, key, encode.LORA_PAYLOAD_VERSION):
|
|
229
|
+
logger.info("Ignoring a stale %s adapter for %s", arch, doc.manifest.name)
|
|
230
|
+
return None
|
|
231
|
+
files = [str(f.get("path")) for f in entry.get("files") or [] if f.get("path")]
|
|
232
|
+
if not files:
|
|
233
|
+
return None
|
|
234
|
+
root = _cache_root() / digest / key
|
|
235
|
+
target = root / Path(files[0]).name
|
|
236
|
+
if not target.is_file():
|
|
237
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
238
|
+
data = doc.members.get(files[0])
|
|
239
|
+
if data is None:
|
|
240
|
+
return None
|
|
241
|
+
target.write_bytes(data)
|
|
242
|
+
return target
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _description(doc: cf.CharDoc) -> str:
|
|
246
|
+
raw = doc.members.get(str(doc.manifest.text.get("path") or ""))
|
|
247
|
+
return raw.decode("utf-8", errors="replace") if raw else ""
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _recompile(doc: cf.CharDoc, path: Path, arch: str = encode.FLUX2_KLEIN_ARCH) -> cf.CharDoc:
|
|
251
|
+
"""Rebuild a stale payload from ``refs/``. Payloads are cache, so this always works."""
|
|
252
|
+
logger.info("Recompiling the %s payload for %s", arch, path.name)
|
|
253
|
+
import io
|
|
254
|
+
|
|
255
|
+
from PIL import Image
|
|
256
|
+
|
|
257
|
+
images: list[Any] = []
|
|
258
|
+
for ref in doc.manifest.refs:
|
|
259
|
+
raw = doc.members.get(str(ref.get("path")))
|
|
260
|
+
if raw is None:
|
|
261
|
+
raise cf.CharFileError(
|
|
262
|
+
f"{path.name} is missing reference {ref.get('path')}, so it cannot be rebuilt."
|
|
263
|
+
)
|
|
264
|
+
images.append(Image.open(io.BytesIO(raw)).convert("RGB"))
|
|
265
|
+
|
|
266
|
+
encode.build_payload(doc.manifest, doc.members, images, arch)
|
|
267
|
+
cf.write(path, doc)
|
|
268
|
+
return doc
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def _extract(doc: cf.CharDoc, digest: str, arch: str) -> tuple[list[AssetRef], list[str]]:
|
|
272
|
+
payload = doc.manifest.payloads.get(arch) or {}
|
|
273
|
+
entries = list(payload.get("files") or [])
|
|
274
|
+
files = [str(entry.get("path")) for entry in entries]
|
|
275
|
+
roles = [cf.role_of(entry) for entry in entries]
|
|
276
|
+
if not files:
|
|
277
|
+
return [], []
|
|
278
|
+
|
|
279
|
+
root = _cache_root() / digest
|
|
280
|
+
marker = root / ".complete"
|
|
281
|
+
if not marker.is_file():
|
|
282
|
+
staging = root.with_name(f"{digest}.part")
|
|
283
|
+
shutil.rmtree(staging, ignore_errors=True)
|
|
284
|
+
staging.mkdir(parents=True, exist_ok=True)
|
|
285
|
+
for member in files:
|
|
286
|
+
data = doc.members.get(member)
|
|
287
|
+
if data is None:
|
|
288
|
+
raise cf.CharFileError(f"Character payload is missing {member}.")
|
|
289
|
+
(staging / Path(member).name).write_bytes(data)
|
|
290
|
+
(staging / ".complete").write_text("ok")
|
|
291
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
292
|
+
staging.replace(root)
|
|
293
|
+
_prune(_cache_root())
|
|
294
|
+
|
|
295
|
+
return [AssetRef(ref="path", path=str(root / Path(member).name)) for member in files], roles
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def _prune(root: Path) -> None:
|
|
299
|
+
"""Oldest-first eviction against a size cap, the same shape as the prompt-embed cache."""
|
|
300
|
+
if not root.is_dir():
|
|
301
|
+
return
|
|
302
|
+
entries = sorted(
|
|
303
|
+
(p for p in root.iterdir() if p.is_dir()),
|
|
304
|
+
key=lambda p: p.stat().st_mtime,
|
|
305
|
+
reverse=True,
|
|
306
|
+
)
|
|
307
|
+
total = 0
|
|
308
|
+
for entry in entries:
|
|
309
|
+
total += sum(f.stat().st_size for f in entry.rglob("*") if f.is_file())
|
|
310
|
+
if total > _CACHE_LIMIT_BYTES:
|
|
311
|
+
shutil.rmtree(entry, ignore_errors=True)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""The ``.char`` container: a persistent character as one portable zip.
|
|
2
|
+
|
|
3
|
+
::
|
|
4
|
+
|
|
5
|
+
Ada.char
|
|
6
|
+
manifest.json first member; the signature is at byte 0
|
|
7
|
+
refs/000.png user-provided reference images, immutable
|
|
8
|
+
derived/face_000.png auto-generated crops
|
|
9
|
+
text/description.md the locked character description
|
|
10
|
+
payloads/<arch>/ compiled per model family, pure cache
|
|
11
|
+
scoring/ identity centroids, one per scoring encoder
|
|
12
|
+
|
|
13
|
+
Refs and text are truth; payloads and centroids are cache, and must be regenerable from ``refs/``.
|
|
14
|
+
Torch-free and network-free, so a core install can read a character.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import hashlib
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import zipfile
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
#: A reader refuses anything above this rather than half-loading the wrong face.
|
|
28
|
+
FORMAT_VERSION = 1
|
|
29
|
+
|
|
30
|
+
MAGIC = "INLINECHAR"
|
|
31
|
+
|
|
32
|
+
#: Valid JSON *and* a signature, so a file is identifiable from 32 bytes without parsing.
|
|
33
|
+
SIGNATURE = f'{{"magic":"{MAGIC}","format_version":'.encode()
|
|
34
|
+
|
|
35
|
+
MANIFEST_NAME = "manifest.json"
|
|
36
|
+
|
|
37
|
+
#: Fixed so the signature lands at byte 0 and two writes produce identical bytes.
|
|
38
|
+
_KEY_ORDER = (
|
|
39
|
+
"magic", "format_version", "char_id", "name", "created_at", "modified_at", "app",
|
|
40
|
+
"app_version", "refs", "derived", "text", "payloads", "scoring", "hints", "apply", "reserved",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
#: Where a reference came from. Nested inside `refs` rather than a new top-level key on purpose:
|
|
45
|
+
#: `from_json` models only the keys it knows, so a top-level addition is destroyed by any older
|
|
46
|
+
#: build that rewrites the file, and three code paths rewrite one routinely.
|
|
47
|
+
ORIGIN_ORIGINAL = "original"
|
|
48
|
+
ORIGIN_HARVESTED = "harvested"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def origin_of(ref: dict[str, Any]) -> str:
|
|
52
|
+
"""Absent means original: every reference written before harvesting existed is one."""
|
|
53
|
+
return str(ref.get("origin") or ORIGIN_ORIGINAL)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
#: What a reference is *of*. Sits beside `origin` for the same reason, and absent means face, so
|
|
57
|
+
#: every character written before roles existed keeps behaving exactly as it did.
|
|
58
|
+
ROLE_FACE = "face"
|
|
59
|
+
ROLE_BODY = "body"
|
|
60
|
+
ROLE_CLOTH = "cloth"
|
|
61
|
+
ROLES = (ROLE_FACE, ROLE_BODY, ROLE_CLOTH)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def role_of(ref: dict[str, Any]) -> str:
|
|
65
|
+
"""A reference's role, defaulting to face. An unknown value reads as face rather than raising:
|
|
66
|
+
a manifest is user data, and refusing to open a character over one bad string helps nobody."""
|
|
67
|
+
role = str(ref.get("role") or ROLE_FACE)
|
|
68
|
+
return role if role in ROLES else ROLE_FACE
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def by_role(refs: list[dict[str, Any]], role: str) -> list[dict[str, Any]]:
|
|
72
|
+
"""The references carrying one role, in manifest order."""
|
|
73
|
+
return [ref for ref in refs if role_of(ref) == role]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class CharFileError(Exception):
|
|
77
|
+
"""A ``.char`` that cannot be trusted. The message is shown to the user."""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class Manifest:
|
|
82
|
+
char_id: str
|
|
83
|
+
name: str
|
|
84
|
+
created_at: int
|
|
85
|
+
modified_at: int
|
|
86
|
+
app_version: str = ""
|
|
87
|
+
refs: list[dict[str, Any]] = field(default_factory=list)
|
|
88
|
+
derived: list[dict[str, Any]] = field(default_factory=list)
|
|
89
|
+
text: dict[str, Any] = field(default_factory=dict)
|
|
90
|
+
payloads: dict[str, Any] = field(default_factory=dict)
|
|
91
|
+
scoring: dict[str, Any] = field(default_factory=dict)
|
|
92
|
+
hints: list[str] = field(default_factory=list)
|
|
93
|
+
#: arch -> "reference" | "lora". Absent means "whatever this model does best", resolved at
|
|
94
|
+
#: apply time, so an older file needs no migration.
|
|
95
|
+
apply: dict[str, str] = field(default_factory=dict)
|
|
96
|
+
reserved: dict[str, Any] = field(
|
|
97
|
+
default_factory=lambda: {"adapters": {}, "video_payloads": {}, "members": []}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
def to_json(self) -> dict[str, Any]:
|
|
101
|
+
return {
|
|
102
|
+
"magic": MAGIC,
|
|
103
|
+
"format_version": FORMAT_VERSION,
|
|
104
|
+
"char_id": self.char_id,
|
|
105
|
+
"name": self.name,
|
|
106
|
+
"created_at": self.created_at,
|
|
107
|
+
"modified_at": self.modified_at,
|
|
108
|
+
"app": "inline-studio",
|
|
109
|
+
"app_version": self.app_version,
|
|
110
|
+
"refs": self.refs,
|
|
111
|
+
"derived": self.derived,
|
|
112
|
+
"text": self.text,
|
|
113
|
+
"payloads": self.payloads,
|
|
114
|
+
"scoring": self.scoring,
|
|
115
|
+
"hints": self.hints,
|
|
116
|
+
"apply": self.apply,
|
|
117
|
+
"reserved": self.reserved,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def from_json(cls, raw: dict[str, Any]) -> Manifest:
|
|
122
|
+
return cls(
|
|
123
|
+
char_id=str(raw.get("char_id", "")),
|
|
124
|
+
name=str(raw.get("name", "")),
|
|
125
|
+
created_at=int(raw.get("created_at", 0)),
|
|
126
|
+
modified_at=int(raw.get("modified_at", 0)),
|
|
127
|
+
app_version=str(raw.get("app_version", "")),
|
|
128
|
+
refs=list(raw.get("refs") or []),
|
|
129
|
+
derived=list(raw.get("derived") or []),
|
|
130
|
+
text=dict(raw.get("text") or {}),
|
|
131
|
+
payloads=dict(raw.get("payloads") or {}),
|
|
132
|
+
scoring=dict(raw.get("scoring") or {}),
|
|
133
|
+
hints=list(raw.get("hints") or []),
|
|
134
|
+
apply={str(k): str(v) for k, v in (raw.get("apply") or {}).items()},
|
|
135
|
+
reserved=dict(raw.get("reserved") or {}),
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@dataclass
|
|
140
|
+
class CharDoc:
|
|
141
|
+
"""A whole character in memory: the manifest plus every member's bytes."""
|
|
142
|
+
|
|
143
|
+
manifest: Manifest
|
|
144
|
+
members: dict[str, bytes] = field(default_factory=dict)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def sha256_bytes(data: bytes) -> str:
|
|
148
|
+
return hashlib.sha256(data).hexdigest()
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def member_name(prefix: str, index: int, suffix: str) -> str:
|
|
152
|
+
"""``refs/000.png`` - always ASCII, since a user's filename may not extract on Windows."""
|
|
153
|
+
return f"{prefix}/{index:03d}{suffix}"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def dumps_manifest(manifest: Manifest) -> bytes:
|
|
157
|
+
"""Manifest bytes with the signature at byte 0."""
|
|
158
|
+
raw = manifest.to_json()
|
|
159
|
+
ordered = {key: raw[key] for key in _KEY_ORDER if key in raw}
|
|
160
|
+
data = json.dumps(ordered, ensure_ascii=True, separators=(",", ":")).encode()
|
|
161
|
+
if not data.startswith(SIGNATURE):
|
|
162
|
+
raise CharFileError("Character manifest lost its signature; the key order is wrong.")
|
|
163
|
+
return data
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def looks_like_char(data: bytes) -> bool:
|
|
167
|
+
"""Whether a manifest blob carries our signature. Cheap enough for an upload gate."""
|
|
168
|
+
return data.startswith(SIGNATURE)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def read(path: Path | str) -> CharDoc:
|
|
172
|
+
"""Load a ``.char``. Raises `CharFileError` with user-facing copy on anything unreadable."""
|
|
173
|
+
target = Path(path)
|
|
174
|
+
try:
|
|
175
|
+
with zipfile.ZipFile(target, "r") as archive:
|
|
176
|
+
names = archive.namelist()
|
|
177
|
+
if MANIFEST_NAME not in names:
|
|
178
|
+
raise CharFileError(f"{target.name} is not a character file (no manifest).")
|
|
179
|
+
raw = archive.read(MANIFEST_NAME)
|
|
180
|
+
members = {
|
|
181
|
+
n: archive.read(n)
|
|
182
|
+
for n in names
|
|
183
|
+
if n != MANIFEST_NAME and not n.endswith("/")
|
|
184
|
+
}
|
|
185
|
+
except zipfile.BadZipFile as error:
|
|
186
|
+
raise CharFileError(f"{target.name} is not a readable character file.") from error
|
|
187
|
+
|
|
188
|
+
if not looks_like_char(raw):
|
|
189
|
+
raise CharFileError(f"{target.name} is not a character file (bad signature).")
|
|
190
|
+
try:
|
|
191
|
+
parsed = json.loads(raw)
|
|
192
|
+
except json.JSONDecodeError as error:
|
|
193
|
+
raise CharFileError(f"{target.name} has a corrupt manifest.") from error
|
|
194
|
+
|
|
195
|
+
version = int(parsed.get("format_version", 0))
|
|
196
|
+
if version > FORMAT_VERSION:
|
|
197
|
+
raise CharFileError(
|
|
198
|
+
f"{target.name} was written by a newer version of Inline Studio "
|
|
199
|
+
f"(character format {version}, this build reads {FORMAT_VERSION}). Update to open it."
|
|
200
|
+
)
|
|
201
|
+
return CharDoc(manifest=Manifest.from_json(parsed), members=members)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def write(path: Path | str, doc: CharDoc) -> Path:
|
|
205
|
+
"""Write atomically. The archive closes before the replace, or Windows refuses it."""
|
|
206
|
+
target = Path(path)
|
|
207
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
208
|
+
staging = target.with_name(target.name + ".part")
|
|
209
|
+
try:
|
|
210
|
+
with zipfile.ZipFile(staging, "w", zipfile.ZIP_DEFLATED) as archive:
|
|
211
|
+
# Manifest first so a reader can identify the file from the front of the stream.
|
|
212
|
+
archive.writestr(MANIFEST_NAME, dumps_manifest(doc.manifest))
|
|
213
|
+
for name in sorted(doc.members):
|
|
214
|
+
# PNGs are already compressed; deflating them again costs time and saves nothing.
|
|
215
|
+
compress = zipfile.ZIP_STORED if name.endswith(".png") else zipfile.ZIP_DEFLATED
|
|
216
|
+
archive.writestr(name, doc.members[name], compress_type=compress)
|
|
217
|
+
os.replace(staging, target)
|
|
218
|
+
except Exception:
|
|
219
|
+
staging.unlink(missing_ok=True)
|
|
220
|
+
raise
|
|
221
|
+
return target
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def refs_fingerprint(manifest: Manifest, policy: dict[str, Any]) -> str:
|
|
225
|
+
"""Ordered ref hashes plus policy, so a reorder or a resize-budget change both invalidate.
|
|
226
|
+
|
|
227
|
+
Originals only. A harvested reference in here would mark the trained adapter stale the moment
|
|
228
|
+
one was taken, and `_extract_lora` drops a stale adapter with an INFO log - so the loop whose
|
|
229
|
+
whole point is a better adapter would silently switch off the adapter the user already has.
|
|
230
|
+
"""
|
|
231
|
+
kept = [ref for ref in manifest.refs if origin_of(ref) == ORIGIN_ORIGINAL]
|
|
232
|
+
parts = [str(ref.get("sha256", "")) for ref in kept]
|
|
233
|
+
parts.append(json.dumps(policy, sort_keys=True, separators=(",", ":")))
|
|
234
|
+
return hashlib.sha256("\x1f".join(parts).encode()).hexdigest()
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def refs_identity(manifest: Manifest) -> str:
|
|
238
|
+
"""Which reference set this is, order included and policy excluded.
|
|
239
|
+
|
|
240
|
+
Separate from `refs_fingerprint`, which answers "is this payload still valid" and so covers
|
|
241
|
+
only the originals and the policy. This answers "were two nodes handed the same character",
|
|
242
|
+
which a payload node needs because it compiles from a doc it is not the one holding.
|
|
243
|
+
"""
|
|
244
|
+
parts = [f"{ref.get('sha256', '')}:{origin_of(ref)}" for ref in manifest.refs]
|
|
245
|
+
return hashlib.sha256("\x1f".join(parts).encode()).hexdigest()
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def payload_valid(manifest: Manifest, arch: str, encoder_version: str) -> bool:
|
|
249
|
+
"""Whether ``payloads/<arch>/`` can be used as-is, or must be recompiled from ``refs/``."""
|
|
250
|
+
payload = manifest.payloads.get(arch)
|
|
251
|
+
if not isinstance(payload, dict):
|
|
252
|
+
return False
|
|
253
|
+
encoder = payload.get("encoder")
|
|
254
|
+
if not isinstance(encoder, dict) or str(encoder.get("version", "")) != encoder_version:
|
|
255
|
+
return False
|
|
256
|
+
policy = payload.get("policy")
|
|
257
|
+
if not isinstance(policy, dict):
|
|
258
|
+
return False
|
|
259
|
+
return str(payload.get("source_sha256", "")) == refs_fingerprint(manifest, policy)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def centroid_valid(manifest: Manifest, encoder_id: str, encoder_version: str) -> bool:
|
|
263
|
+
"""Cosine similarity across two encoder builds is meaningless, so the version must match."""
|
|
264
|
+
encoders = manifest.scoring.get("encoders")
|
|
265
|
+
if not isinstance(encoders, list):
|
|
266
|
+
return False
|
|
267
|
+
for entry in encoders:
|
|
268
|
+
if isinstance(entry, dict) and str(entry.get("id")) == encoder_id:
|
|
269
|
+
return str(entry.get("version", "")) == encoder_version
|
|
270
|
+
return False
|