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 上仓库的模型卡总会写明推荐数值。

Batch from a JSON file — prompt & filename as data从 JSON 文件批量生成——把提示词与文件名当数据

Hard-coding one prompt is fine for a test, but the real win of a script is running many unattended. Move the two things that change — the description and the output filename — out of the code and into a small prompts.json. Now anyone can add images by editing data, never the Python.写死一个提示词用来测试没问题,但脚本真正的价值是无人值守地批量运行很多个。把会变的两样东西——描述文本和输出文件名——从代码里挪到一个小小的 prompts.json 中。这样谁都能通过改数据来添加图片,而不用碰 Python。

// prompts.json  —  a list of { description, filename } jobs
[
  { "prompt": "misty Doi Suthep temple at dawn, lanna architecture, highly detailed", "out": "temple.png" },
  { "prompt": "lantern festival on the Ping River at dusk, warm light, reflections",   "out": "lanterns.png" },
  { "prompt": "a bowl of khao soi on a wooden table, steam, soft window light",        "out": "khaosoi.png" }
]

Then change t2i.py to load that file and loop. Build the pipeline once (the slow part), then reuse it for every job:然后把 t2i.py 改成读取该文件并循环。pipeline 只构建一次(最慢的一步),之后每个任务都复用它:

# t2i.py  —  read prompts + filenames from prompts.json, generate each
import json, torch
from diffusers import AutoPipelineForText2Image

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

# build the pipeline ONCE, outside the loop (loading ~2.5 GB is the slow step)
pipe = AutoPipelineForText2Image.from_pretrained(
    "stabilityai/sd-turbo", torch_dtype=dtype,
).to(device)
pipe.enable_attention_slicing()

# load the job list — encoding="utf-8" so Thai / Chinese prompts survive
with open("prompts.json", encoding="utf-8") as f:
    jobs = json.load(f)

for job in jobs:
    image = pipe(
        prompt=job["prompt"],            # description ← from JSON
        num_inference_steps=4,
        guidance_scale=0.0,
        generator=torch.Generator(device).manual_seed(42),
    ).images[0]
    image.save(job["out"])              # filename ← from JSON
    print("saved", job["out"])
python t2i.py        # generates temple.png, lanterns.png, khaosoi.png in one run
Why this is the whole point为什么这正是关键所在 The code never changes again — you describe what you want in prompts.json and run. That file can be hand-written, generated by another program, or come from a spreadsheet export. Add "steps" or "seed" keys per job and read them with job.get("seed", 42) if you want per-image control. This is the leap a UI can't make.代码再也不用改了——你在 prompts.json 里描述想要什么,然后运行即可。这个文件可以手写、可以由另一个程序生成、也可以来自表格导出。如果想对每张图单独控制,给每个任务加 "steps" 或 "seed" 字段,并用 job.get("seed", 42) 读取。这一步是界面无法做到的飞跃。

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

4Making 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 在本地驱动它们全部,中间没有任何界面挡路。