Text-to-Image & Image-to-Video in Python用 Python 跑文生图与图生视频

No ComfyUI, no node graph — just a .py file and the diffusers library. The same models and weights ComfyUI runs, called directly. Everything below works locally on CPU; a GPU only makes it faster.不用 ComfyUI,没有节点图——只要一个 .py 文件和 diffusers 库。和 ComfyUI 跑的是同样的模型与权重,只是直接调用。下面的一切都能在本地 CPU 上运行;GPU 只是让它更快。

library: diffusers text→image: sd-turbo image→video: SVD device: CPU ok docker: ddtraveller/diffusers-cpu

★The big idea — ComfyUI is just a wrapper核心思想——ComfyUI 只是一层外壳

ComfyUI looks like magic because it paints a graph of nodes. But under the hood every node is calling Hugging Face diffusers + PyTorch — the same library you can import yourself. A diffusers pipeline is one Python object that bundles every node ComfyUI wires by hand:ComfyUI 看起来很神奇,因为它画出一张节点图。但底层每个节点都在调用 Hugging Face 的 diffusers + PyTorch——也就是你自己可以 import 的那个库。一个 diffusers pipeline 就是一个 Python 对象,把 ComfyUI 手工连接的每个节点都打包在一起:

ComfyUI nodeComfyUI 节点 diffusers equivalentdiffusers 中的对应
Load CheckpointAutoPipelineForText2Image.from_pretrained(...)
CLIP Text Encode (positive / negative)prompt= / negative_prompt= arguments参数
KSamplernum_inference_steps, guidance_scale, generator
VAE Decodeautomatic — happens inside the pipeline自动——在 pipeline 内部完成
Save Imageimage.save("out.png")
prompt (text)提示词(文本)→ pipe(...)→ image.png→ pipe_i2v(image)→ video.mp4
Why do this instead of ComfyUI?为什么不直接用 ComfyUI? A script is reproducible and automatable — run it in a loop, on a schedule, inside a web service, or over 500 prompts unattended. No browser, no clicking, no canvas. ComfyUI is great for exploring; a Python file is how you ship.脚本是可复现、可自动化的——可以循环运行、定时运行、放进 Web 服务里,或无人值守地跑 500 个提示词。不用浏览器、不用点击、不用画布。ComfyUI 适合探索;Python 文件才是用来交付的。

1Setup — one pip install环境准备——一次 pip 安装

You need Python 3.10+ and these packages. torch CPU wheels are large but install with one command; no CUDA, no drivers required.你需要 Python 3.10+ 和以下这些包。torch 的 CPU 版本体积较大,但一条命令即可安装;无需 CUDA、无需驱动。

# a clean virtual environment keeps this off your system Python
python -m venv .venv
# Windows:  .venv\Scripts\activate    ·    macOS/Linux:  source .venv/bin/activate

pip install diffusers transformers accelerate safetensors
pip install torch                        # CPU build is the default
pip install imageio imageio-ffmpeg pillow  # for saving the video file
Where do the models go?模型下载到哪里? from_pretrained() downloads weights once and caches them. You never manage .safetensors files by hand like in ComfyUI — diffusers fetches and stores them under:
~/.cache/huggingface/hub (macOS/Linux) · C:\Users\<you>\.cache\huggingface\hub (Windows). Delete that folder to free space.
from_pretrained() 会只下载一次权重并缓存。你不必像在 ComfyUI 里那样手动管理 .safetensors 文件——diffusers 自动获取并存放在:
~/.cache/huggingface/hub(macOS/Linux)· C:\Users\<你>\.cache\huggingface\hub(Windows)。删掉这个目录即可释放空间。

Prefer zero setup? Use the Docker image不想装环境?直接用 Docker 镜像

If you'd rather not install torch and the rest, everything above is pre-baked into ddtraveller/diffusers-cpu — CPU-only, multi-arch (Intel + Apple Silicon/ARM). The two tools below, t2i and i2v, are exactly the scripts from sections 2 and 3, already on the image's PATH.如果你不想安装 torch 等一堆依赖,上面这一切都已经打包进 ddtraveller/diffusers-cpu 镜像——纯 CPU、多架构(Intel + Apple Silicon/ARM)。下面的两个工具 t2i 和 i2v 就是第 2、3 节里的脚本,已经放在镜像的 PATH 上。

# 1 — pull it
docker pull ddtraveller/diffusers-cpu

# 2 — make two host folders: one for outputs, one to cache downloaded models
mkdir -p ~/diffusers/work ~/diffusers/cache

# text -> image (first run downloads sd-turbo into the cache, then it's instant)
docker run --rm \
  -v ~/diffusers/work:/work \
  -v ~/diffusers/cache:/cache \
  ddtraveller/diffusers-cpu \
  t2i --prompt "misty Doi Suthep temple at dawn, lanna style" --out /work/temple.png

# image -> video (SVD is gated — pass your Hugging Face token)
docker run --rm -e HF_TOKEN=hf_xxx \
  -v ~/diffusers/work:/work \
  -v ~/diffusers/cache:/cache \
  ddtraveller/diffusers-cpu \
  i2v --image /work/temple.png --out /work/temple.mp4

Your temple.png / temple.mp4 land in ~/diffusers/work on your host. The /cache mount means models download once and are reused next run. Run docker run --rm ddtraveller/diffusers-cpu t2i --help to see every flag (--model, --steps, --seed, …).生成的 temple.png / temple.mp4 会出现在宿主机的 ~/diffusers/work 里。挂载 /cache 意味着模型只下载一次,下次运行直接复用。运行 docker run --rm ddtraveller/diffusers-cpu t2i --help 可查看所有参数(--model、--steps、--seed……)。

On Windows, replace ~/diffusers/... with a full path and use ${PWD} (PowerShell) or %cd% (cmd) for the current folder.在 Windows 上,把 ~/diffusers/... 换成完整路径,当前目录用 ${PWD}(PowerShell)或 %cd%(cmd)。

↓Download the model — two ways (on a Mac)下载模型——两种方法(以 Mac 为例)

Using stabilityai/sd-turbo as the example: it is ungated — no Hugging Face login, no license to accept — and the download is about 2.5 GB. Both methods below write to the same cache, so your script finds the weights either way. You only need one.以 stabilityai/sd-turbo 为例:它是非受限模型——不需要登录 Hugging Face、不需要接受许可——下载约 2.5 GB。下面两种方法写入的是同一个缓存,所以脚本都能找到权重。你只需选其一。

Method A — let the script download it (nothing extra to run)方法 A——让脚本自动下载(无需额外操作)

The first time from_pretrained() runs, it fetches the weights and caches them; every run after that loads from disk. This is the whole "download step" — there isn't a separate one.第一次执行 from_pretrained() 时会拉取权重并缓存;之后每次运行都从磁盘加载。这就是全部的“下载步骤”——并没有单独的一步。

# run the t2i.py from section 2 — the download happens on this line, once:
#   pipe = AutoPipelineForText2Image.from_pretrained("stabilityai/sd-turbo", ...)
python t2i.py        # first run pulls ~2.5 GB into the cache, then a few seconds per image

Method B — pre-download with the hf CLI方法 B——用 hf 命令行提前下载

Use this to grab the weights ahead of time (offline prep, or to inspect the files). It installs nothing your script doesn't already need.用它可以提前把权重拉下来(离线准备,或想查看文件)。它安装的东西脚本本来也需要。

pip install -U "huggingface_hub[cli]"

# downloads the repo into ~/.cache/huggingface/hub — your script finds it automatically
hf download stabilityai/sd-turbo

# …or put the files in a folder you can see, instead of the hidden cache:
hf download stabilityai/sd-turbo --local-dir ./sd-turbo

# confirm what landed (size + on-disk path):
hf cache scan

hf download is the current command; the older huggingface-cli download still works but is deprecated. No hf auth login is needed for sd-turbo — that step is only for gated models like SVD in section 3.hf download 是现在的命令;旧的 huggingface-cli download 仍可用但已弃用。sd-turbo 不需要 hf auth login——那一步只对第 3 节里 SVD 这类受限模型才需要。

On Apple Silicon (M1–M4), use the GPUApple Silicon(M1–M4)上请用 GPU The section 2 script falls back to "cpu" on any non-CUDA box. On a Mac, swap in Apple's Metal backend for a big speed-up — set device = "mps" and dtype = torch.float16. With "mps" the variant="fp16" argument also halves the download:第 2 节的脚本在非 CUDA 机器上会退回 "cpu"。在 Mac 上换成 Apple 的 Metal 后端能大幅提速——设 device = "mps"、dtype = torch.float16。配合 "mps" 时,variant="fp16" 还能把下载体积减半:
pipe = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/sd-turbo", torch_dtype=torch.float16, variant="fp16",
).to("mps")

Cache location on macOS: ~/.cache/huggingface/hub. Delete that folder (or run hf cache delete) to free the space. To pull only the smaller fp16 weights via the CLI: hf download stabilityai/sd-turbo --include "*.json" "*.txt" "*fp16*".macOS 上的缓存位置:~/.cache/huggingface/hub。删除该目录(或运行 hf cache delete)即可释放空间。只想用命令行拉取较小的 fp16 权重:hf download stabilityai/sd-turbo --include "*.json" "*.txt" "*fp16*"。

2Text → image文生图

The whole program is four lines of real work. We use SD-Turbo: a distilled model that produces a 512×512 image in 1–4 steps (regular Stable Diffusion needs ~25), so it finishes in seconds even on CPU. Turbo models are trained without classifier-free guidance, so set guidance_scale=0.0.真正干活的代码就四行。我们用 SD-Turbo:一个蒸馏模型,1–4 步就能生成 512×512 的图(普通 Stable Diffusion 需要约 25 步),所以即便在 CPU 上也几秒完成。Turbo 模型训练时没有使用 classifier-free guidance,所以要设 guidance_scale=0.0。

# t2i.py  —  text to image, runs on CPU
import torch
from diffusers import AutoPipelineForText2Image

# pick the best device available; CPU needs float32 (float16 is GPU-only)
device = "cuda" if torch.cuda.is_available() else "cpu"
dtype  = torch.float16 if device == "cuda" else torch.float32

# 1 — Load Checkpoint (downloads + caches on first run)
pipe = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/sd-turbo", torch_dtype=dtype,
).to(device)
pipe.enable_attention_slicing()        # less RAM on small machines

# 2 — CLIP Text Encode + KSampler + VAE Decode, all in one call
image = pipe(
    prompt="misty Doi Suthep temple at dawn, golden light, lanna architecture, highly detailed",
    num_inference_steps=4,      # sd-turbo: 1 is fastest, 4 is a touch nicer
    guidance_scale=0.0,         # turbo models: CFG OFF
    generator=torch.Generator(device).manual_seed(42),  # reproducible
).images[0]

# 3 — Save Image
image.save("temple.png")
print("saved temple.png")
python t2i.py        # first run downloads ~2.5 GB, then a few seconds per image on CPU

Swapping models — change one string换模型——改一个字符串

The only thing that changes between models is the repo name in from_pretrained() and the step/guidance settings. The pipeline class auto-detects the architecture.在不同模型之间,唯一要改的就是 from_pretrained() 里的仓库名,以及步数 / guidance 设置。pipeline 类会自动识别模型结构。

Model (repo id)模型(仓库 id) Settings设置 Use it for适用于
stabilityai/sd-turbo1–4 steps, CFG 0, 512²1–4 步,CFG 0,512²Fastest — the default above.最快——上面的默认。
stabilityai/sdxl-turbo1–4 steps, CFG 0, 512–10241–4 步,CFG 0,512–1024Higher quality, slower on CPU.质量更高,CPU 上更慢。
stable-diffusion-v1-5/stable-diffusion-v1-5~25 steps, CFG 7, 512²约 25 步,CFG 7,512²The classic SD1.5; needs a negative_prompt.经典 SD1.5;需要 negative_prompt。
Match steps & guidance to the model步数与 guidance 要匹配模型 A turbo model run at 25 steps with CFG 7 looks worse, not better — it's distilled for few steps and no guidance. Likewise a normal SD1.5 model at 1 step / CFG 0 outputs noise. The repo's model card on Hugging Face always states the recommended values.把 turbo 模型用 25 步、CFG 7 去跑,结果会更差而不是更好——它是为少步数、无 guidance 蒸馏的。同理,普通 SD1.5 用 1 步 / CFG 0 只会输出噪点。Hugging Face 上仓库的模型卡总会写明推荐数值。

3Image → video图生视频

Same shape, different pipeline. Stable Video Diffusion (SVD) takes one still image and predicts a short clip of motion from it. The base model makes 14 frames; ...-xt makes 25. Feed it the temple.png you just generated.套路一样,换个 pipeline。Stable Video Diffusion(SVD)接收一张静态图,并据此预测一小段运动画面。基础模型生成 14 帧;...-xt 生成 25 帧。把你刚生成的 temple.png 喂给它。

# i2v.py  —  image to video
import torch
from diffusers import StableVideoDiffusionPipeline
from diffusers.utils import load_image, export_to_video

device = "cuda" if torch.cuda.is_available() else "cpu"
dtype  = torch.float16 if device == "cuda" else torch.float32

pipe = StableVideoDiffusionPipeline.from_pretrained(
    "stabilityai/stable-video-diffusion-img2vid", torch_dtype=dtype,
).to(device)
pipe.enable_attention_slicing()

# SVD wants a specific size; resize the still first
image = load_image("temple.png").resize((1024, 576))

frames = pipe(
    image,
    num_frames=14,           # base = 14, the -xt model = 25
    decode_chunk_size=2,     # lower = less memory (decode fewer frames at once)
    generator=torch.Generator(device).manual_seed(42),
).frames[0]

export_to_video(frames, "temple.mp4", fps=7)
print("saved temple.mp4")
Honest warning — SVD on CPU is slow老实说——SVD 在 CPU 上很慢 Text-to-image with a turbo model is seconds on CPU. Video is a different beast: SVD denoises 14–25 frames at once, so on a CPU expect many minutes to over an hour for one short clip. It runs and the code is correct — it's just patient work without a GPU. Start with num_frames=14 and decode_chunk_size=2.用 turbo 模型做文生图在 CPU 上是几秒。视频则完全不同:SVD 一次要对 14–25 帧去噪,所以在 CPU 上一段短视频可能要 几十分钟到一个多小时。代码是对的、也能跑——只是没有 GPU 就得有耐心。先从 num_frames=14、decode_chunk_size=2 开始。
SVD is a gated model — log in firstSVD 是受限模型——需先登录 The SVD weights require accepting a license on its Hugging Face page, then authenticating once so the download is allowed:SVD 权重需要先在其 Hugging Face 页面接受许可,然后登录一次,下载才会被放行:
huggingface-cli login        # paste a token from huggingface.co/settings/tokens

4Reference many .md files & images in one render在一次渲染中引用多个 .md 文件和图片

Real projects don't hard-code the prompt string. A cartoon series keeps its look, its characters, and each shot in separate .md files, and locks the actual appearance in reference .pngs — a "show bible". At render time a script reads them all, concatenates the text into one prompt, and feeds the images into the pipeline. That is the whole trick to keeping frame #800 on-model with frame #1. (See the companion guide Prepare Once, Animate Forever for how the bible is authored.)真实项目不会把提示词写死。一部卡通剧集把画风、角色和每个镜头分别放在不同的 .md 文件里,并用参考 .png 锁定真实外观——一本「设定集」。渲染时,脚本会把它们全部读入、把文本拼成一条提示词、并把图片喂进 pipeline。这就是让第 800 帧与第 1 帧保持一致的全部诀窍。(设定集如何撰写见姊妹篇 准备一次,永远开拍。)

style.mdcharacters/*.mdscenes/scene_NN.md*.png→ assemble组装→ pipe(...)→ scene_01.png→ pipe_i2v(...)→ scene_01.mp4

A — Concatenate the text from several .md filesA——把多个 .md 文件的文本拼接起来

Each bible file is plain YAML after its # title line, so one tiny loader reads any of them. The scene names which characters and location to use; the script resolves those names to the locked tokens and joins everything into a single prompt. One extra dependency: pip install pyyaml.每个设定集文件在 # 标题 行之后就是普通 YAML,所以一个极小的加载函数就能读取任意一个。场景文件指明用哪些角色和地点;脚本把这些名字解析成锁定的 token,再把一切拼成一条 prompt。多一个依赖:pip install pyyaml。

# render_scene.py — assemble ONE prompt from many .md files, then render
import yaml, torch
from pathlib import Path
from diffusers import AutoPipelineForText2Image

def fields(path):
    # a .md bible file is YAML once you drop its '# title' line
    body = "".join(l for l in open(path, encoding="utf-8") if not l.startswith("#"))
    return yaml.safe_load(body)

style = fields("style.md")                  # render_tokens, negative, model
chars = {p.stem: fields(p) for p in Path("characters").glob("*.md")}
scene = fields("scenes/scene_01.md")        # cast, action, camera, motion

# resolve every character named in the scene -> their locked look_tokens
looks  = [chars[name]["look_tokens"] for name in scene["cast"]]
prompt = ", ".join(looks + [scene["action"], scene["camera"], style["render_tokens"]])

pipe = AutoPipelineForText2Image.from_pretrained(style["model"], torch_dtype=torch.float32)
image = pipe(
    prompt=prompt,
    negative_prompt=style["negative"],
    num_inference_steps=28, guidance_scale=6.5,   # animagine-xl (a normal SDXL model, not turbo)
).images[0]
Path("renders").mkdir(exist_ok=True)
image.save("renders/scene_01.png")
Reference, don't repeat引用,而不是重复 The scene file says cast: [ploy], not a 40-word description. Change Ploy's hairpin once in characters/ploy.md and every scene that lists her inherits it on the next render — no find-and-replace across hundreds of prompts.场景文件写的是 cast: [ploy],而不是 40 个词的描述。在 characters/ploy.md 里改一次 Ploy 的发夹,下次渲染时每个用到她的场景都会继承——不必在上百条提示词里查找替换。

B — Feed several reference images at once (IP-Adapter)B——一次喂入多张参考图片(IP-Adapter)

Words drift; pictures don't. To pin the actual drawing — the character turnaround, the location backdrop — pass them as pixels. IP-Adapter is the standard way, and it takes more than one image: load a list of adapters, set a list of scales, and pass a list to ip_adapter_image — one slot per adapter. Here adapter 1 holds the character, adapter 2 holds the place.文字会走样,图片不会。要锁定真实画作——角色转面图、场景背景——就把它们作为像素传入。IP-Adapter 是标准做法,而且能接收不止一张图:加载一个列表的适配器、设置一个列表的权重、并把一个列表传给 ip_adapter_image——每个适配器对应一个槽位。这里适配器 1 装角色,适配器 2 装地点。

# reference the actual PICTURES, not just words — several at once
from diffusers.utils import load_image

pipe.load_ip_adapter(
    "h94/IP-Adapter", subfolder="sdxl_models",
    weight_name=[
        "ip-adapter-plus-face_sdxl_vit-h.safetensors",  # adapter 1 -> the character
        "ip-adapter_sdxl_vit-h.safetensors",            # adapter 2 -> the place / style
    ],
)
pipe.set_ip_adapter_scale([0.7, 0.4])    # per-adapter strength (a list, one per adapter)

image = pipe(
    prompt=prompt,
    negative_prompt=style["negative"],
    ip_adapter_image=[                       # a LIST -> one entry per loaded adapter
        load_image(chars["ploy"]["refs"][0]),         # characters/ploy_turnaround.png
        load_image("locations/village_lane_wide.png"),
    ],
    num_inference_steps=30, guidance_scale=6.5,
).images[0]

The refs: list comes straight from characters/ploy.md — the YAML loader already parsed it, so chars["ploy"]["refs"][0] is the turnaround path. A single still you want to lightly redraw instead goes through AutoPipelineForImage2Image with image= + a strength= (0.3 keeps it close, 0.8 reimagines it).refs: 列表直接来自 characters/ploy.md——YAML 加载器已经解析好了,所以 chars["ploy"]["refs"][0] 就是转面图路径。若只想对单张图做轻度重绘,则用 AutoPipelineForImage2Image 配 image= 和 strength=(0.3 贴近原图,0.8 大幅重构)。

C — The same references carry into the videoC——同一组参考延续到视频

Because the still you just rendered already is every reference composed into one frame, animating it needs no new references — the composed PNG becomes the video's first frame.因为你刚渲染的静帧本身就是所有参考合成的一帧,让它动起来无需新的参考——这张合成 PNG 直接成为视频的第一帧。

# the composed still IS the video's reference: feed it straight to SVD as frame 0
from diffusers.utils import load_image, export_to_video
seed   = load_image("renders/scene_01.png").resize((1024, 576))
frames = pipe_i2v(seed, num_frames=14, decode_chunk_size=2).frames[0]
export_to_video(frames, "renders/scene_01.mp4", fps=7)

# SVD takes no text prompt. For a text-driven i2v model (e.g. Wan 2.2), the
# scene's `motion:` line becomes the motion prompt, with this still as frame 0.

Where every source lands in the call每个来源在调用中的落点

Source file来源文件 Field字段 Goes into进入
style.mdmodelfrom_pretrained(...) repo id仓库 id
style.mdrender_tokensappended to every附加到每条 prompt
style.mdnegativenegative_prompt=
characters/*.mdlook_tokensresolved from由 scene.cast into解析进 prompt
characters/*.mdrefs (png)ip_adapter_image= / init初始图 image=
locations/*.png—ip_adapter_image= / i2v seed framei2v 种子帧
scenes/scene_NN.mdaction, cameraprompt
scenes/scene_NN.mdmotionmotion prompt (Wan-style i2v)运动提示词(Wan 类 i2v)
scenes/scene_NN.mdsecondsclip length /片段时长 / num_frames
Why this is the whole game为什么这就是全部 Once the loader exists, rendering a new shot is: write a short scene_NN.md, run the script. No prompt is ever retyped, no reference re-described — the .md files and .pngs are the inputs, and the pipeline call is the same every time. That's the line between "exploring in a UI" and "shipping a series".一旦加载器就位,渲染一个新镜头就是:写一个简短的 scene_NN.md,运行脚本。提示词永不重打、参考永不重述——.md 文件和 .png 就是输入,pipeline 调用每次都一样。这正是「在界面里探索」与「交付一部剧集」的分界线。

5Making it fit & run faster让它跑得下、跑得快

On a 16–24 GB machine, two flags do most of the work. They trade a little speed for a lot less peak memory — the difference between "runs" and "killed: out of memory".在 16–24 GB 内存的机器上,两个开关就能解决大部分问题。它们用一点速度换取大幅降低的峰值内存——这正是“能跑”与“被系统杀掉:内存不足”的区别。

Call调用What it does作用
pipe.enable_attention_slicing()Computes attention in chunks — biggest single RAM saver.分块计算注意力——单项最省内存。
pipe.enable_vae_slicing()Decodes the image/frames in slices (helps SVD most).分片解码图像 / 帧(对 SVD 帮助最大)。
pipe.enable_model_cpu_offload()GPU only — keeps idle sub-models on CPU, moving each to GPU just in time. Frees VRAM.仅 GPU——把空闲的子模型留在 CPU,需要时才搬到 GPU。释放显存。
num_inference_steps ↓Fewer steps = faster. Turbo models are built for this.步数越少越快。Turbo 模型正是为此而生。
Reproducibility — the seed可复现性——随机种子 Pass a fixed generator=torch.Generator(device).manual_seed(N) and the same prompt + settings produce the exact same image every run — across machines. This is the part a UI makes fiddly and a script makes trivial.传入固定的 generator=torch.Generator(device).manual_seed(N),相同的提示词和设置每次都会生成完全相同的图——跨机器也一样。这正是界面里麻烦、而脚本里轻而易举的地方。

?Recap小结

Text → image文生图 Image → video图生视频
Pipeline classPipeline 类AutoPipelineForText2ImageStableVideoDiffusionPipeline
Default model默认模型stabilityai/sd-turbostabilityai/stable-video-diffusion-img2vid
Input输入a text prompt一段文本提示词a still image一张静态图
Output输出image.save("out.png")export_to_video(...,"out.mp4")
CPU speedCPU 速度seconds (turbo)几秒(turbo)minutes–hours几十分钟–数小时
Gated?是否受限?No否Yes — accept license + login是——需接受许可并登录
The one thing to remember最该记住的一点 Every diffusion tool — ComfyUI, Automatic1111, a web demo, this script — is calling the same handful of diffusers pipelines. Learn from_pretrained(...) → pipe(...) → .save() and you can drive all of them from plain Python, locally, with no UI in the way.每一个扩散模型工具——ComfyUI、Automatic1111、网页 demo、还有这个脚本——调用的都是同一小撮 diffusers pipeline。掌握 from_pretrained(...) → pipe(...) → .save(),你就能用纯 Python 在本地驱动它们全部,中间没有任何界面挡路。