UNPKG

stable-diffusion-client

Version:
541 lines (540 loc) 22.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const dntShim = __importStar(require("../../_dnt.shims.js")); const deps_js_1 = require("../deps.js"); const StableDiffusionResult_js_1 = __importDefault(require("./StableDiffusionResult.js")); const ControlNetApi_js_1 = __importDefault(require("./ControlNetApi.js")); const utils_js_1 = require("../utils.js"); const createScriptsWithCnUnits = async (initScripts, controlNetUnit) => { const promises = controlNetUnit.map(async (unit) => await unit.toJson()); const args = await Promise.all(promises); const ControlNet = { args }; const scripts = { ...initScripts, ControlNet }; return scripts; }; /** * @class StableDiffusionApi * @classdesc Stable Diffusion API, a translation layer for [Automatic1111's Stable Diffusion API](https://github.com/AUTOMATIC1111/stable-diffusion-webui) * @param {StableDiffusionApiConfig} config - Configuration object * @property {StableDiffusionApiConfig} config - Configuration object * @property {axios.AxiosInstance} api - Axios instance * @property {ControlNetApi} controlNet - ControlNet API * @example * const api = new StableDiffusionApi() * const result = await api.txt2img({ * prompt: "A computer that has more brain power than a human being", * batch_size: 2, * }) * * // Save the first image * result.image.toFile("result.png") * * // Save all images * result.images.forEach((image, i) => { * image.toFile(`result_${i}.png`) * }) */ class StableDiffusionApi { constructor({ host = "127.0.0.1", port = 7860, protocol = "http", timeout = 30000, baseUrl = null, defaultSampler = "Euler a", defaultStepCount = 20, } = {}) { Object.defineProperty(this, "config", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "baseURL", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "basicAuth", { enumerable: true, configurable: true, writable: true, value: "" }); Object.defineProperty(this, "ControlNet", { enumerable: true, configurable: true, writable: true, value: new ControlNetApi_js_1.default(this) }); this.baseURL = new URL(baseUrl || `${protocol}://${host}${port ? `:${port}` : ""}`); this.config = { host, port, protocol, timeout, baseUrl, defaultSampler, defaultStepCount, }; } /** * Set the authentication for the axios API * @param {string} username * @param {string} password * @returns {StableDiffusionApi} this StableDiffusionApi instance */ setAuth(username, password) { if (username && password) { this.basicAuth = "Basic" + (0, deps_js_1.encodeBase64)(username + ":" + password); } return this; } async get(uri) { const controller = new AbortController(); const headers = { "Content-Type": "application/json", }; if (this.basicAuth) { headers.Authorization = this.basicAuth; } const options = { method: "GET", signal: controller.signal, headers: { "Content-Type": "application/json", }, }; const promise = dntShim.fetch(new URL(uri, this.baseURL), options); const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); const response = await promise; clearTimeout(timeoutId); // TODO handle error return response.json(); } async post(uri, body) { const controller = new AbortController(); const headers = { "Content-Type": "application/json", }; if (this.basicAuth) { headers.Authorization = this.basicAuth; } const options = { method: "POST", signal: controller.signal, body: JSON.stringify(body), headers: { "Content-Type": "application/json", }, }; if (body !== undefined) options.body = JSON.stringify(body); const promise = dntShim.fetch(new URL(uri, this.baseURL), options); const timeoutId = setTimeout(() => controller.abort(), this.config.timeout); const response = await promise; clearTimeout(timeoutId); // TODO handle error return response.json(); } /** * Stable Diffusion txt2img call * @param {Txt2ImgOptions} options * @returns {Promise<StableDiffusionResult>} ApiResult containing the generated image(s) * @memberof StableDiffusionApi * @async * @example * const api = new StableDiffusionApi(); * const result = await api.txt2img({ * prompt: "An angry artist that claims that the Stable Diffusion model contains an exact copy of their artwork", * }); */ async txt2img(options) { let alwayson_scripts = await createScriptsWithCnUnits(options.alwayson_scripts, options.controlnet_units ?? []); if (alwayson_scripts.ControlNet.args.length === 0) { alwayson_scripts = {}; } const response = await this.post("/sdapi/v1/txt2img", { enable_hr: options.enable_hr ?? false, hr_scale: options.hr_scale ?? 2, hr_upscaler: options.hr_upscaler ?? "Latent", hr_second_pass_steps: options.hr_second_pass_steps ?? 0, hr_resize_x: options.hr_resize_x ?? 0, hr_resize_y: options.hr_resize_y ?? 0, denoising_strength: options.denoising_strength ?? 0.7, firstphase_width: options.firstphase_width ?? 0, firstphase_height: options.firstphase_height ?? 0, prompt: options.prompt ?? "", styles: options.styles ?? [], seed: options.seed ?? -1, subseed: options.subseed ?? -1, subseed_strength: options.subseed_strength ?? 0.0, seed_resize_from_h: options.seed_resize_from_h ?? 0, seed_resize_from_w: options.seed_resize_from_w ?? 0, batch_size: options.batch_size ?? 1, n_iter: options.n_iter ?? 1, steps: options.steps ?? this.config.defaultStepCount, cfg_scale: options.cfg_scale ?? 7.0, width: options.width ?? 512, height: options.height ?? 512, restore_faces: options.restore_faces ?? false, tiling: options.tiling ?? false, do_not_save_samples: options.do_not_save_samples ?? false, do_not_save_grid: options.do_not_save_grid ?? false, negative_prompt: options.negative_prompt ?? "", eta: options.eta ?? 1.0, s_churn: options.s_churn ?? 0, s_tmax: options.s_tmax ?? 0, s_tmin: options.s_tmin ?? 0, s_noise: options.s_noise ?? 1, override_settings: options.override_settings ?? {}, override_settings_restore_afterwards: options.override_settings_restore_afterwards ?? true, script_args: options.script_args ?? [], script_name: options.script_name ?? null, send_images: options.send_images ?? true, save_images: options.save_images ?? false, alwayson_scripts, sampler_name: options.sampler_name ?? this.config.defaultSampler, use_deprecated_controlnet: options.use_deprecated_controlnet ?? false, }); return new StableDiffusionResult_js_1.default(response); } /** * Stable Diffusion img2img call * @param {Img2ImgOptions} options Options for the img2img call * @returns {Promise<StableDiffusionResult>} ApiResult containing the generated image(s) * @memberof StableDiffusionApi * @async * @example * const api = new StableDiffusionApi(); * const init_image = sharp("dog.png"); * const result = await api.img2img({ * init_images: [init_image], * prompt: "Just a funky disco dog", * }); */ async img2img(options) { const init_images = await Promise.all(options.init_images.map(async (image) => await (0, utils_js_1.toBase64)(image))); const mask = options.mask_image ? await (0, utils_js_1.toBase64)(options.mask_image) : null; const alwayson_scripts = await createScriptsWithCnUnits(options.alwayson_scripts, options.controlnet_units ?? []); const response = await this.post("/sdapi/v1/img2img", { init_images, resize_mode: options.resize_mode ?? 0, denoising_strength: options.denoising_strength ?? 0.75, image_cfg_scale: options.image_cfg_scale ?? 1.5, mask, mask_blur: options.mask_blur ?? 4, inpainting_fill: options.inpainting_fill ?? 0, inpaint_full_res: options.inpaint_full_res ?? true, inpaint_full_res_padding: options.inpaint_full_res_padding ?? 0, inpainting_mask_invert: options.inpainting_mask_invert ?? 0, initial_noise_multiplier: options.initial_noise_multiplier ?? 1, prompt: options.prompt ?? "", styles: options.styles ?? [], seed: options.seed ?? -1, subseed: options.subseed ?? -1, subseed_strength: options.subseed_strength ?? 0, seed_resize_from_h: options.seed_resize_from_h ?? 0, seed_resize_from_w: options.seed_resize_from_w ?? 0, sampler_name: options.sampler_name ?? this.config.defaultSampler, batch_size: options.batch_size ?? 1, n_iter: options.n_iter ?? 1, steps: options.steps ?? this.config.defaultStepCount, cfg_scale: options.cfg_scale ?? 7.0, width: options.width ?? 512, height: options.height ?? 512, restore_faces: options.restore_faces ?? false, tiling: options.tiling ?? false, do_not_save_samples: options.do_not_save_samples ?? false, do_not_save_grid: options.do_not_save_grid ?? false, negative_prompt: options.negative_prompt ?? "", eta: options.eta ?? 1.0, s_churn: options.s_churn ?? 0, s_tmax: options.s_tmax ?? 0, s_tmin: options.s_tmin ?? 0, s_noise: options.s_noise ?? 1, override_settings: options.override_settings ?? {}, override_settings_restore_afterwards: options.override_settings_restore_afterwards ?? true, script_args: options.script_args ?? [], include_init_images: options.include_init_images ?? false, script_name: options.script_name ?? null, send_images: options.send_images ?? true, save_images: options.save_images ?? false, alwayson_scripts, use_deprecated_controlnet: options.use_deprecated_controlnet ?? false, }); return new StableDiffusionResult_js_1.default(response); } /** * Stable Diffusion extra's call for single images * @param {ExtraSingleOptions} options Options for the extra's call * @returns {Promise<StableDiffusionResult>} ApiResult containing the generated image(s) * @memberof StableDiffusionApi * @async * @example * const api = new StableDiffusionApi(); * const image = sharp("dog.png"); * const result = await api.extraSingle({ * image, * upscaler_1: "Lanczos", * upscaling_resize: 2, * }); */ async extraSingle(options) { const image = await (0, utils_js_1.toBase64)(options.image); const response = await this.post("/sdapi/v1/extra-single-image", { image, resize_mode: options.resize_mode ?? 0, show_extras_results: options.show_extras_results ?? true, gfpgan_visibility: options.gfpgan_visibility ?? 0, codeformer_weight: options.codeformer_weight ?? 0, upscaling_resize: options.upscaling_resize ?? 2, upscaling_resize_w: options.upscaling_resize_w ?? 512, upscaling_resize_h: options.upscaling_resize_h ?? 512, upscaling_resize_crop: options.upscaling_resize_crop ?? true, upscaler_1: options.upscaler_1 ?? "None", upscaler_2: options.upscaler_2 ?? "None", extras_upscaler_2_visibility: options.extras_upscaler_2_visibility ?? 0, upscale_first: options.upscale_first ?? false, }); return new StableDiffusionResult_js_1.default(response); } /** * Stable Diffusion extra's call for batch images * @param {ExtraBatchOptions} batchOptions Options for the extra's call * @returns {Promise<StableDiffusionResult>} ApiResult containing the generated image(s) * @memberof StableDiffusionApi * @async * @example * const api = new StableDiffusionApi(); * const image1 = sharp("dog.png"); * const image2 = sharp("cat.png"); * const result = await api.extraBatch({ * images: [image1, image2], * name_list: ["dog", "cat"], * upscaler_1: "Lanczos", * upscaling_resize: 2, * }); */ async extraBatch(options) { if (options.images.length !== options.name_list.length) { throw new Error("The number of images and names must be the same in extraBatch"); } const images = await Promise.all(options.images.map(async (image) => await (0, utils_js_1.toBase64)(image))); const image_list = images.map((image, index) => { return { image, name: options.name_list[index], }; }); const response = await this.post("/sdapi/v1/extra-batch-images", { image_list, resize_mode: options.resize_mode ?? 0, show_extras_results: options.show_extras_results ?? true, gfpgan_visibility: options.gfpgan_visibility ?? 0, codeformer_weight: options.codeformer_weight ?? 0, upscaling_resize: options.upscaling_resize ?? 2, upscaling_resize_w: options.upscaling_resize_w ?? 512, upscaling_resize_h: options.upscaling_resize_h ?? 512, upscaling_resize_crop: options.upscaling_resize_crop ?? true, upscaler_1: options.upscaler_1 ?? "None", upscaler_2: options.upscaler_2 ?? "None", extras_upscaler_2_visibility: options.extras_upscaler_2_visibility ?? 0, upscale_first: options.upscale_first ?? false, }); return new StableDiffusionResult_js_1.default(response); } /** * Gets the info of a png image * @param {Sharp} image Image to get info from * @returns {Promise<StableDiffusionResult>} ApiResult containing the info */ async pngInfo(image) { const image_data = await (0, utils_js_1.toBase64)(image); const response = await this.post("/sdapi/v1/png-info", { image: image_data, }); return new StableDiffusionResult_js_1.default(response); } /** * Interrogates an image with an interrogation model * @param {Sharp} image Image to interrogate * @param model Model to use for interrogation * @returns {Promise<StableDiffusionResult>} The result of the interrogation */ async interrogate(image, model) { const image_data = await (0, utils_js_1.toBase64)(image); const response = await this.post("/sdapi/v1/interrogate", { image: image_data, }); return new StableDiffusionResult_js_1.default(response); } // TODO add other options async getOptions() { const response = await this.get("/sdapi/v1/options"); return response; } async setOptions(options) { const response = await this.post("/sdapi/v1/options", options); return response; } /** * Gets the progress status of the current session * @param {boolean} skipCurrentImage True to skip the current image, false to include it * @returns {Promise<Progress>} The progress status of the current session */ async getProgress(skipCurrentImage = false) { const response = await this.get(`/sdapi/v1/progress?skipCurrentImage=${skipCurrentImage}`); return response; } /** * Gets the list of command line flags that are available * @returns {Promise<Record<string, unknown>>} The list of command line flags that are available */ async getCmdFlags() { const response = await this.get("/sdapi/v1/cmd-flags"); return response; } /** * Gets the list of samplers * @returns {Promise<Sampler[]>} The list of samplers */ async getSamplers() { const response = await this.get("/sdapi/v1/samplers"); return response; } /** * Gets the list of upscalers * @returns {Promise<Upscaler[]>} The list of upscalers */ async getUpscalers() { const response = await this.get("/sdapi/v1/upscalers"); return response; } /** * Gets the list of Stable Diffusion models * @returns {Promise<StableDiffusionModel[]>} The list of Stable Diffusion models */ async getSdModels() { const response = await this.get("/sdapi/v1/sd-models"); return response; } /** * Gets the list of hypernetworks * @returns {Promise<HyperNetwork[]>} The list of hypernetworks */ async getHypernetworks() { const response = await this.get("/sdapi/v1/hypernetworks"); return response; } /** * Gets the list of face restorers * @returns {Promise<FaceRestorer[]>} The list of face restorers */ async getFaceRestorers() { const response = await this.get("/sdapi/v1/face-restorers"); return response; } /** * Gets the list of Real-ESRGAN models * @returns {Promise<RealESRGanModel[]>} The list of Real-ESRGAN models */ async getRealesrganModels() { const response = await this.get("/sdapi/v1/realesrgan-models"); return response; } /** * Gets the list of Stable Diffusion prompt styles * @returns {Promise<PromptStyle[]>} The list of prompt styles */ async getPromptStyles() { const response = await this.get("/sdapi/v1/prompt-styles"); return response; } /** * Refreshes the list of Stable Diffusion checkpoints * @returns {Promise<void>} */ async refreshCheckpoints() { await this.post("/sdapi/v1/refresh-checkpoints"); } /** * Gets the name of the current Stable Diffusion checkpoint being used * @returns {Promise<string>} The name of the current Stable Diffusion checkpoint being used */ async getCurrentModel() { const options = await this.getOptions(); return options.sd_model_checkpoint; } /** * Sets the Stable Diffusion checkpoint to use * @param name Name of the model to set. * @param findClosest If true, will try to find the closest model name if the exact name is not found * @returns {Promise<void>} */ async setModel(name, findClosest = true) { const models = await this.getSdModels(); const modelNames = models.map((model) => model.model_name); let foundModel = null; if (modelNames.includes(name)) { foundModel = name; } else if (findClosest) { const fzf = new deps_js_1.Fzf(modelNames); const [bestMatch] = fzf.find(name); if (bestMatch && bestMatch.score > 0.5) { foundModel = bestMatch.item; } } if (foundModel) { const options = { sd_model_checkpoint: foundModel, }; await this.setOptions(options); } else { throw new Error("Model not found"); } } /** * Waits for the Stable Diffusion server to be ready to accept new requests * @param checkInterval Interval in seconds to check progress * @returns {Promise<boolean>} Only resolves when progress is 0.0 and job_count is 0 */ waitForReady(checkInterval = 5.0) { return new Promise((resolve, _reject) => { const interval = setInterval(async () => { const result = await this.getProgress(); const progress = result.progress; const jobCount = result.state.job_count; if (progress === 0.0 && jobCount === 0) { clearInterval(interval); resolve(true); } else { console.log(`[WAIT]: progress = ${progress.toFixed(4)}, job_count = ${jobCount}`); } }, checkInterval * 1000); }); } } exports.default = StableDiffusionApi;