nai-aclab 1.0.8 → 1.0.10
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.
- package/README.md +4 -4
- package/app.py +2 -15
- package/bin/nai-artist-lab.js +1 -12
- package/package.json +1 -1
- package/web/index.html +3 -4
- package/web/main.js +136 -74
- package/web/styles.css +1 -1
- package/web_app.py +11 -0
package/README.md
CHANGED
|
@@ -219,11 +219,11 @@ NovelAI V4+의 멀티 캐릭터 프롬프트는 캐릭터를 쉼표로 합치지
|
|
|
219
219
|
|
|
220
220
|
앱 설정과 생성 이미지는 내 컴퓨터에 저장됩니다.
|
|
221
221
|
|
|
222
|
-
기본적으로
|
|
222
|
+
기본적으로 홈 폴더 아래에 `nai_aclab` 폴더를 만들고, 그 안에 설정과 생성 이미지를 함께 저장합니다.
|
|
223
223
|
|
|
224
|
-
- Windows: `
|
|
225
|
-
- macOS: `~/
|
|
226
|
-
- Linux: `
|
|
224
|
+
- Windows: `C:\Users\사용자이름\nai_aclab`
|
|
225
|
+
- macOS: `~/nai_aclab`
|
|
226
|
+
- Linux: `~/nai_aclab`
|
|
227
227
|
|
|
228
228
|
폴더 안의 구조는 아래처럼 나뉩니다.
|
|
229
229
|
|
package/app.py
CHANGED
|
@@ -25,25 +25,11 @@ except Exception:
|
|
|
25
25
|
APP_DIR = Path(__file__).resolve().parent
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def default_pictures_dir() -> Path:
|
|
29
|
-
if os.name != "nt":
|
|
30
|
-
user_dirs = Path.home() / ".config" / "user-dirs.dirs"
|
|
31
|
-
try:
|
|
32
|
-
for line in user_dirs.read_text(encoding="utf-8").splitlines():
|
|
33
|
-
if line.startswith("XDG_PICTURES_DIR="):
|
|
34
|
-
value = line.split("=", 1)[1].strip().strip('"')
|
|
35
|
-
value = value.replace("$HOME", str(Path.home()))
|
|
36
|
-
return Path(value).expanduser()
|
|
37
|
-
except OSError:
|
|
38
|
-
pass
|
|
39
|
-
return Path.home() / "Pictures"
|
|
40
|
-
|
|
41
|
-
|
|
42
28
|
def default_user_dir() -> Path:
|
|
43
29
|
configured = os.environ.get("NAI_ARTIST_LAB_USER_DIR", "").strip()
|
|
44
30
|
if configured:
|
|
45
31
|
return Path(configured).expanduser()
|
|
46
|
-
return
|
|
32
|
+
return Path.home() / "nai_aclab"
|
|
47
33
|
|
|
48
34
|
|
|
49
35
|
USER_DIR = default_user_dir()
|
|
@@ -330,6 +316,7 @@ class BattingScene:
|
|
|
330
316
|
name: str = ""
|
|
331
317
|
base_preset: str = ""
|
|
332
318
|
character_preset: str = ""
|
|
319
|
+
image_size: str = "portrait"
|
|
333
320
|
count: int = 2
|
|
334
321
|
|
|
335
322
|
|
package/bin/nai-artist-lab.js
CHANGED
|
@@ -14,18 +14,7 @@ function userDataDir() {
|
|
|
14
14
|
return process.env.NAI_ARTIST_LAB_USER_DIR;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
return path.join(os.homedir(), "Pictures", "NAI Artist Combination Lab");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (process.platform === "darwin") {
|
|
22
|
-
return path.join(os.homedir(), "Pictures", "NAI Artist Combination Lab");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return path.join(
|
|
26
|
-
process.env.XDG_PICTURES_DIR || path.join(os.homedir(), "Pictures"),
|
|
27
|
-
"NAI Artist Combination Lab"
|
|
28
|
-
);
|
|
17
|
+
return path.join(os.homedir(), "nai_aclab");
|
|
29
18
|
}
|
|
30
19
|
|
|
31
20
|
function printHelp() {
|
package/package.json
CHANGED
package/web/index.html
CHANGED
|
@@ -38,10 +38,9 @@
|
|
|
38
38
|
<h1 id="pageTitle">생성</h1>
|
|
39
39
|
<p id="pageSubtitle">프리셋, 작가 조합, 생성 상태를 한 화면에서 조정합니다.</p>
|
|
40
40
|
</div>
|
|
41
|
-
<div class="top-actions">
|
|
42
|
-
<button class="ghost-button" id="refreshButton">새로고침</button>
|
|
43
|
-
|
|
44
|
-
</div>
|
|
41
|
+
<div class="top-actions">
|
|
42
|
+
<button class="ghost-button" id="refreshButton">새로고침</button>
|
|
43
|
+
</div>
|
|
45
44
|
</header>
|
|
46
45
|
|
|
47
46
|
<section id="generate" class="tab-page active">
|
package/web/main.js
CHANGED
|
@@ -40,23 +40,42 @@ const pageCopy = {
|
|
|
40
40
|
history: ["히스토리", "생성 결과를 훑어보고 이상형 월드컵으로 선호 조합을 고릅니다."],
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
const apiFields = [
|
|
44
|
-
["token", "API 토큰", "password"],
|
|
45
|
-
["endpoint", "Endpoint", "text"],
|
|
46
|
-
["model", "Model", "text"],
|
|
47
|
-
["steps", "Steps", "number"],
|
|
48
|
-
["scale", "Scale", "number"],
|
|
49
|
-
["guidance_rescale", "Guidance Rescale", "number"],
|
|
50
|
-
["sampler", "Sampler", "
|
|
51
|
-
["noise_schedule", "Noise Schedule", "
|
|
52
|
-
["seed", "Seed (-1=random)", "number"],
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
43
|
+
const apiFields = [
|
|
44
|
+
["token", "API 토큰", "password"],
|
|
45
|
+
["endpoint", "Endpoint", "text"],
|
|
46
|
+
["model", "Model", "text"],
|
|
47
|
+
["steps", "Steps", "number"],
|
|
48
|
+
["scale", "Scale", "number"],
|
|
49
|
+
["guidance_rescale", "Guidance Rescale", "number"],
|
|
50
|
+
["sampler", "Sampler", "select"],
|
|
51
|
+
["noise_schedule", "Noise Schedule", "select"],
|
|
52
|
+
["seed", "Seed (-1=random)", "number"],
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const apiSelectOptions = {
|
|
56
|
+
sampler: [
|
|
57
|
+
["k_euler_ancestral", "Euler Ancestral"],
|
|
58
|
+
["k_euler", "Euler"],
|
|
59
|
+
["k_dpmpp_2m", "DPM++ 2M"],
|
|
60
|
+
["k_dpmpp_2s_ancestral", "DPM++ 2S Ancestral"],
|
|
61
|
+
["k_dpmpp_sde", "DPM++ SDE"],
|
|
62
|
+
["k_dpm_2", "DPM2"],
|
|
63
|
+
["k_dpm_fast", "DPM Fast"],
|
|
64
|
+
["ddim_v3", "DDIM"],
|
|
65
|
+
],
|
|
66
|
+
noise_schedule: [
|
|
67
|
+
["karras", "Karras"],
|
|
68
|
+
["native", "Native"],
|
|
69
|
+
["exponential", "Exponential"],
|
|
70
|
+
["polyexponential", "Polyexponential"],
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const imageSizeOptions = {
|
|
75
|
+
portrait: { label: "Normal Portrait", width: 832, height: 1216 },
|
|
76
|
+
landscape: { label: "Landscape", width: 1216, height: 832 },
|
|
77
|
+
square: { label: "Square", width: 1024, height: 1024 },
|
|
78
|
+
};
|
|
60
79
|
const defaultNovelAiEndpoint = "https://image.novelai.net/ai/generate-image";
|
|
61
80
|
|
|
62
81
|
function escapeHtml(value) {
|
|
@@ -208,12 +227,13 @@ function syncBattingScenesToState() {
|
|
|
208
227
|
return;
|
|
209
228
|
}
|
|
210
229
|
state.batting_scenes = rows.map((row, index) => ({
|
|
211
|
-
name: row.querySelector(".batting-scene-name")?.value.trim() || `Scene ${index + 1}`,
|
|
212
|
-
base_preset: row.querySelector(".batting-scene-base")?.value || "",
|
|
213
|
-
character_preset: row.querySelector(".batting-scene-character")?.value || "",
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
230
|
+
name: row.querySelector(".batting-scene-name")?.value.trim() || `Scene ${index + 1}`,
|
|
231
|
+
base_preset: row.querySelector(".batting-scene-base")?.value || "",
|
|
232
|
+
character_preset: row.querySelector(".batting-scene-character")?.value || "",
|
|
233
|
+
image_size: normalizeImageSizeKey(row.querySelector(".batting-scene-size")?.value || state.generation?.image_size || "portrait"),
|
|
234
|
+
count: Math.max(1, Number(row.querySelector(".batting-scene-count")?.value || 2)),
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
217
237
|
|
|
218
238
|
function fillSelect(select, items, selectedName) {
|
|
219
239
|
select.innerHTML = "";
|
|
@@ -227,24 +247,39 @@ function fillSelect(select, items, selectedName) {
|
|
|
227
247
|
else if (items[0]) select.value = items[0].name;
|
|
228
248
|
}
|
|
229
249
|
|
|
230
|
-
function fillImageSizeSelect() {
|
|
231
|
-
const select = $("#imageSizeSelect");
|
|
232
|
-
if (!select) return;
|
|
233
|
-
select.innerHTML = "";
|
|
234
|
-
for (const [key, option] of Object.entries(imageSizeOptions)) {
|
|
250
|
+
function fillImageSizeSelect() {
|
|
251
|
+
const select = $("#imageSizeSelect");
|
|
252
|
+
if (!select) return;
|
|
253
|
+
select.innerHTML = "";
|
|
254
|
+
for (const [key, option] of Object.entries(imageSizeOptions)) {
|
|
235
255
|
const item = document.createElement("option");
|
|
236
256
|
item.value = key;
|
|
237
257
|
item.textContent = `${option.label}: ${option.width} x ${option.height}`;
|
|
238
258
|
select.appendChild(item);
|
|
239
259
|
}
|
|
240
260
|
const saved = state.generation.image_size;
|
|
241
|
-
select.value = imageSizeOptions[saved] ? saved : "portrait";
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
261
|
+
select.value = imageSizeOptions[saved] ? saved : "portrait";
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function normalizeImageSizeKey(value, fallback = "portrait") {
|
|
265
|
+
if (imageSizeOptions[value]) return value;
|
|
266
|
+
return imageSizeOptions[fallback] ? fallback : "portrait";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function imageSizeOptionsHtml(selectedKey) {
|
|
270
|
+
const selected = normalizeImageSizeKey(selectedKey);
|
|
271
|
+
return Object.entries(imageSizeOptions)
|
|
272
|
+
.map(([key, option]) => {
|
|
273
|
+
const label = `${option.label}: ${option.width} x ${option.height}`;
|
|
274
|
+
return `<option value="${key}" ${key === selected ? "selected" : ""}>${escapeHtml(label)}</option>`;
|
|
275
|
+
})
|
|
276
|
+
.join("");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function applyImageSizeToState(requestState) {
|
|
280
|
+
const selected = imageSizeOptions[requestState.generation?.image_size] || imageSizeOptions.portrait;
|
|
281
|
+
requestState.api = requestState.api || {};
|
|
282
|
+
requestState.api.width = selected.width;
|
|
248
283
|
requestState.api.height = selected.height;
|
|
249
284
|
}
|
|
250
285
|
|
|
@@ -417,17 +452,19 @@ function renderBatting() {
|
|
|
417
452
|
root.innerHTML = `<div class="empty-box">아직 등록된 씬이 없습니다. 씬을 추가해보세요.</div>`;
|
|
418
453
|
return;
|
|
419
454
|
}
|
|
420
|
-
state.batting_scenes.forEach((scene, index) => {
|
|
421
|
-
const row = document.createElement("article");
|
|
422
|
-
row.className = "batting-scene-row";
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
<
|
|
426
|
-
<label><span
|
|
427
|
-
<label><span
|
|
428
|
-
<label><span
|
|
429
|
-
<
|
|
430
|
-
|
|
455
|
+
state.batting_scenes.forEach((scene, index) => {
|
|
456
|
+
const row = document.createElement("article");
|
|
457
|
+
row.className = "batting-scene-row";
|
|
458
|
+
const sceneSize = normalizeImageSizeKey(scene.image_size || state.generation?.image_size || "portrait");
|
|
459
|
+
row.innerHTML = `
|
|
460
|
+
<div class="batting-scene-number">${index + 1}</div>
|
|
461
|
+
<label><span>씬 이름</span><input class="batting-scene-name" value="${escapeHtml(scene.name || `Scene ${index + 1}`)}" /></label>
|
|
462
|
+
<label><span>베이스 + 퀄리티</span><select class="batting-scene-base">${selectOptionsHtml(state.base_presets, scene.base_preset)}</select></label>
|
|
463
|
+
<label><span>캐릭터</span><select class="batting-scene-character">${selectOptionsHtml(state.character_presets, scene.character_preset)}</select></label>
|
|
464
|
+
<label><span>이미지 해상도</span><select class="batting-scene-size">${imageSizeOptionsHtml(sceneSize)}</select></label>
|
|
465
|
+
<label><span>생성 개수</span><input class="batting-scene-count" type="number" min="1" max="200" value="${Math.max(1, Number(scene.count || 2))}" /></label>
|
|
466
|
+
<button class="icon-button batting-scene-delete" type="button" title="씬 삭제">×</button>
|
|
467
|
+
`;
|
|
431
468
|
row.querySelector(".batting-scene-delete").onclick = () => deleteBattingScene(index);
|
|
432
469
|
row.querySelectorAll("input, select").forEach((node) => {
|
|
433
470
|
node.oninput = () => {
|
|
@@ -534,18 +571,38 @@ function renderPresets() {
|
|
|
534
571
|
bindAutosaveInputs(editors);
|
|
535
572
|
}
|
|
536
573
|
|
|
537
|
-
function renderSettings() {
|
|
538
|
-
const form = $("#apiForm");
|
|
539
|
-
form.innerHTML = "";
|
|
540
|
-
for (const [key, label, type] of apiFields) {
|
|
541
|
-
const wrap = document.createElement("label");
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
574
|
+
function renderSettings() {
|
|
575
|
+
const form = $("#apiForm");
|
|
576
|
+
form.innerHTML = "";
|
|
577
|
+
for (const [key, label, type] of apiFields) {
|
|
578
|
+
const wrap = document.createElement("label");
|
|
579
|
+
if (type === "select") {
|
|
580
|
+
wrap.innerHTML = `<span>${label}</span><select id="api_${key}"></select>`;
|
|
581
|
+
} else {
|
|
582
|
+
wrap.innerHTML = `<span>${label}</span><input id="api_${key}" type="${type}" />`;
|
|
583
|
+
}
|
|
584
|
+
form.appendChild(wrap);
|
|
585
|
+
const input = $(`#api_${key}`);
|
|
586
|
+
if (type === "select") {
|
|
587
|
+
input.innerHTML = (apiSelectOptions[key] || [])
|
|
588
|
+
.map(([value, optionLabel]) => `<option value="${escapeHtml(value)}">${escapeHtml(optionLabel)}</option>`)
|
|
589
|
+
.join("");
|
|
590
|
+
const saved = state.api[key] ?? "";
|
|
591
|
+
if (Array.from(input.options).some((option) => option.value === saved)) {
|
|
592
|
+
input.value = saved;
|
|
593
|
+
} else if (saved) {
|
|
594
|
+
const custom = document.createElement("option");
|
|
595
|
+
custom.value = saved;
|
|
596
|
+
custom.textContent = `${saved} (custom)`;
|
|
597
|
+
input.appendChild(custom);
|
|
598
|
+
input.value = saved;
|
|
599
|
+
}
|
|
600
|
+
} else {
|
|
601
|
+
input.value = key === "token" ? "" : state.api[key] ?? "";
|
|
602
|
+
}
|
|
603
|
+
if (key === "token") {
|
|
604
|
+
input.placeholder = state.api.token_saved ? "저장된 토큰을 사용합니다" : "API 토큰을 입력하세요";
|
|
605
|
+
input.autocomplete = "off";
|
|
549
606
|
}
|
|
550
607
|
if (type === "number") input.step = "any";
|
|
551
608
|
}
|
|
@@ -1044,11 +1101,12 @@ function addBattingScene() {
|
|
|
1044
1101
|
state.batting_scenes = state.batting_scenes || [];
|
|
1045
1102
|
const nextIndex = state.batting_scenes.length + 1;
|
|
1046
1103
|
state.batting_scenes.push({
|
|
1047
|
-
name: uniqueName(state.batting_scenes, `Scene ${nextIndex}`),
|
|
1048
|
-
base_preset: state.generation.base_preset || state.base_presets[0]?.name || "",
|
|
1049
|
-
character_preset: state.generation.character_preset || state.character_presets[0]?.name || "",
|
|
1050
|
-
|
|
1051
|
-
|
|
1104
|
+
name: uniqueName(state.batting_scenes, `Scene ${nextIndex}`),
|
|
1105
|
+
base_preset: state.generation.base_preset || state.base_presets[0]?.name || "",
|
|
1106
|
+
character_preset: state.generation.character_preset || state.character_presets[0]?.name || "",
|
|
1107
|
+
image_size: normalizeImageSizeKey(state.generation.image_size || "portrait"),
|
|
1108
|
+
count: 2,
|
|
1109
|
+
});
|
|
1052
1110
|
renderBatting();
|
|
1053
1111
|
scheduleSave();
|
|
1054
1112
|
}
|
|
@@ -1591,19 +1649,24 @@ function switchTab(tab) {
|
|
|
1591
1649
|
}
|
|
1592
1650
|
currentTab = tab;
|
|
1593
1651
|
$$(".nav-item").forEach((button) => button.classList.toggle("active", button.dataset.tab === tab));
|
|
1594
|
-
$$(".tab-page").forEach((page) => page.classList.toggle("active", page.id === tab));
|
|
1652
|
+
$$(".tab-page").forEach((page) => page.classList.toggle("active", page.id === tab));
|
|
1595
1653
|
$("#pageTitle").textContent = pageCopy[tab][0];
|
|
1596
1654
|
$("#pageSubtitle").textContent = pageCopy[tab][1];
|
|
1597
|
-
$("#generateButton").hidden = tab !== "generate";
|
|
1598
1655
|
updateJobActionButtons();
|
|
1599
1656
|
}
|
|
1600
1657
|
|
|
1601
1658
|
async function loadState() {
|
|
1602
1659
|
const data = await request("/api/state");
|
|
1603
|
-
state = data.state;
|
|
1604
|
-
state.quality_override_prompt = state.quality_override_prompt || "";
|
|
1605
|
-
state.batting_scenes = state.batting_scenes || [];
|
|
1606
|
-
|
|
1660
|
+
state = data.state;
|
|
1661
|
+
state.quality_override_prompt = state.quality_override_prompt || "";
|
|
1662
|
+
state.batting_scenes = state.batting_scenes || [];
|
|
1663
|
+
state.generation = state.generation || {};
|
|
1664
|
+
state.generation.image_size = normalizeImageSizeKey(state.generation.image_size || "portrait");
|
|
1665
|
+
state.batting_scenes = state.batting_scenes.map((scene) => ({
|
|
1666
|
+
...scene,
|
|
1667
|
+
image_size: normalizeImageSizeKey(scene.image_size || state.generation.image_size),
|
|
1668
|
+
}));
|
|
1669
|
+
selectedHistoryIds = new Set(Array.from(selectedHistoryIds).filter((id) => state.history.some((item) => item.id === id)));
|
|
1607
1670
|
categoryIndex = Math.min(categoryIndex, Math.max(0, state.categories.length - 1));
|
|
1608
1671
|
baseIndex = Math.min(baseIndex, Math.max(0, state.base_presets.length - 1));
|
|
1609
1672
|
charIndex = Math.min(charIndex, Math.max(0, state.character_presets.length - 1));
|
|
@@ -1617,11 +1680,10 @@ async function loadState() {
|
|
|
1617
1680
|
|
|
1618
1681
|
function bindEvents() {
|
|
1619
1682
|
bindModalImageInteractions();
|
|
1620
|
-
$$(".nav-item").forEach((button) => button.onclick = () => switchTab(button.dataset.tab));
|
|
1621
|
-
$("#refreshButton").onclick = loadState;
|
|
1622
|
-
$("#previewButton").onclick = previewPrompt;
|
|
1623
|
-
$("#
|
|
1624
|
-
$("#generateButtonInline").onclick = startGeneration;
|
|
1683
|
+
$$(".nav-item").forEach((button) => button.onclick = () => switchTab(button.dataset.tab));
|
|
1684
|
+
$("#refreshButton").onclick = loadState;
|
|
1685
|
+
$("#previewButton").onclick = previewPrompt;
|
|
1686
|
+
$("#generateButtonInline").onclick = startGeneration;
|
|
1625
1687
|
$("#stopGenerateButton").onclick = stopGeneration;
|
|
1626
1688
|
$("#clearFixedArtistsButton").onclick = clearFixedArtists;
|
|
1627
1689
|
$("#addBattingSceneButton").onclick = addBattingScene;
|
package/web/styles.css
CHANGED
|
@@ -406,7 +406,7 @@ pre {
|
|
|
406
406
|
|
|
407
407
|
.batting-scene-row {
|
|
408
408
|
display: grid;
|
|
409
|
-
grid-template-columns: 42px minmax(
|
|
409
|
+
grid-template-columns: 42px minmax(140px, 0.9fr) minmax(170px, 1fr) minmax(170px, 1fr) minmax(190px, 1fr) 100px 38px;
|
|
410
410
|
gap: 10px;
|
|
411
411
|
align-items: end;
|
|
412
412
|
padding: 12px;
|
package/web_app.py
CHANGED
|
@@ -48,6 +48,11 @@ JOBS: dict[str, dict] = {}
|
|
|
48
48
|
STATE_LOCK = threading.Lock()
|
|
49
49
|
LOCAL_API_TOKEN = secrets.token_urlsafe(32)
|
|
50
50
|
ACTIVE_JOB_STATUSES = {"queued", "running", "cancelling"}
|
|
51
|
+
IMAGE_SIZE_DIMENSIONS = {
|
|
52
|
+
"portrait": (832, 1216),
|
|
53
|
+
"landscape": (1216, 832),
|
|
54
|
+
"square": (1024, 1024),
|
|
55
|
+
}
|
|
51
56
|
|
|
52
57
|
|
|
53
58
|
def dataclass_from_dict(cls, data: dict):
|
|
@@ -349,6 +354,12 @@ def scene_state(state: AppState, scene: BattingScene) -> AppState:
|
|
|
349
354
|
request_state = deepcopy(state)
|
|
350
355
|
request_state.generation.base_preset = scene.base_preset
|
|
351
356
|
request_state.generation.character_preset = scene.character_preset
|
|
357
|
+
image_size = scene.image_size if scene.image_size in IMAGE_SIZE_DIMENSIONS else request_state.generation.image_size
|
|
358
|
+
if image_size in IMAGE_SIZE_DIMENSIONS:
|
|
359
|
+
width, height = IMAGE_SIZE_DIMENSIONS[image_size]
|
|
360
|
+
request_state.generation.image_size = image_size
|
|
361
|
+
request_state.api.width = width
|
|
362
|
+
request_state.api.height = height
|
|
352
363
|
request_state.generation.count = scene_count(scene)
|
|
353
364
|
return request_state
|
|
354
365
|
|