nai-aclab 1.0.9 → 1.0.11
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/app.py +1 -0
- package/package.json +1 -1
- package/web/index.html +3 -4
- package/web/main.js +172 -94
- package/web/styles.css +7 -1
- package/web_app.py +11 -0
package/app.py
CHANGED
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) {
|
|
@@ -130,20 +149,26 @@ function scheduleSave() {
|
|
|
130
149
|
saveTimer = setTimeout(saveNow, 450);
|
|
131
150
|
}
|
|
132
151
|
|
|
133
|
-
async function saveNow() {
|
|
134
|
-
if (!state) return;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
152
|
+
async function saveNow() {
|
|
153
|
+
if (!state) return;
|
|
154
|
+
const tokenInput = $("#api_token");
|
|
155
|
+
const hadPendingToken = Boolean(tokenInput?.value.trim());
|
|
156
|
+
syncEditorsToState();
|
|
157
|
+
try {
|
|
158
|
+
const data = await request("/api/state", {
|
|
159
|
+
method: "POST",
|
|
160
|
+
body: JSON.stringify({ state }),
|
|
161
|
+
});
|
|
162
|
+
state = data.state;
|
|
163
|
+
if (hadPendingToken && tokenInput) {
|
|
164
|
+
tokenInput.value = "";
|
|
165
|
+
tokenInput.placeholder = "저장된 토큰을 사용합니다. 새 토큰을 입력하면 교체됩니다.";
|
|
166
|
+
}
|
|
167
|
+
setSaveState(hadPendingToken ? "API 토큰 변경 저장됨" : "자동 저장됨");
|
|
168
|
+
} catch (error) {
|
|
169
|
+
setSaveState("저장 실패");
|
|
170
|
+
console.error(error);
|
|
171
|
+
}
|
|
147
172
|
}
|
|
148
173
|
|
|
149
174
|
function syncEditorsToState() {
|
|
@@ -208,12 +233,13 @@ function syncBattingScenesToState() {
|
|
|
208
233
|
return;
|
|
209
234
|
}
|
|
210
235
|
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
|
-
}
|
|
236
|
+
name: row.querySelector(".batting-scene-name")?.value.trim() || `Scene ${index + 1}`,
|
|
237
|
+
base_preset: row.querySelector(".batting-scene-base")?.value || "",
|
|
238
|
+
character_preset: row.querySelector(".batting-scene-character")?.value || "",
|
|
239
|
+
image_size: normalizeImageSizeKey(row.querySelector(".batting-scene-size")?.value || state.generation?.image_size || "portrait"),
|
|
240
|
+
count: Math.max(1, Number(row.querySelector(".batting-scene-count")?.value || 2)),
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
217
243
|
|
|
218
244
|
function fillSelect(select, items, selectedName) {
|
|
219
245
|
select.innerHTML = "";
|
|
@@ -227,24 +253,39 @@ function fillSelect(select, items, selectedName) {
|
|
|
227
253
|
else if (items[0]) select.value = items[0].name;
|
|
228
254
|
}
|
|
229
255
|
|
|
230
|
-
function fillImageSizeSelect() {
|
|
231
|
-
const select = $("#imageSizeSelect");
|
|
232
|
-
if (!select) return;
|
|
233
|
-
select.innerHTML = "";
|
|
234
|
-
for (const [key, option] of Object.entries(imageSizeOptions)) {
|
|
256
|
+
function fillImageSizeSelect() {
|
|
257
|
+
const select = $("#imageSizeSelect");
|
|
258
|
+
if (!select) return;
|
|
259
|
+
select.innerHTML = "";
|
|
260
|
+
for (const [key, option] of Object.entries(imageSizeOptions)) {
|
|
235
261
|
const item = document.createElement("option");
|
|
236
262
|
item.value = key;
|
|
237
263
|
item.textContent = `${option.label}: ${option.width} x ${option.height}`;
|
|
238
264
|
select.appendChild(item);
|
|
239
265
|
}
|
|
240
266
|
const saved = state.generation.image_size;
|
|
241
|
-
select.value = imageSizeOptions[saved] ? saved : "portrait";
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
267
|
+
select.value = imageSizeOptions[saved] ? saved : "portrait";
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function normalizeImageSizeKey(value, fallback = "portrait") {
|
|
271
|
+
if (imageSizeOptions[value]) return value;
|
|
272
|
+
return imageSizeOptions[fallback] ? fallback : "portrait";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function imageSizeOptionsHtml(selectedKey) {
|
|
276
|
+
const selected = normalizeImageSizeKey(selectedKey);
|
|
277
|
+
return Object.entries(imageSizeOptions)
|
|
278
|
+
.map(([key, option]) => {
|
|
279
|
+
const label = `${option.label}: ${option.width} x ${option.height}`;
|
|
280
|
+
return `<option value="${key}" ${key === selected ? "selected" : ""}>${escapeHtml(label)}</option>`;
|
|
281
|
+
})
|
|
282
|
+
.join("");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function applyImageSizeToState(requestState) {
|
|
286
|
+
const selected = imageSizeOptions[requestState.generation?.image_size] || imageSizeOptions.portrait;
|
|
287
|
+
requestState.api = requestState.api || {};
|
|
288
|
+
requestState.api.width = selected.width;
|
|
248
289
|
requestState.api.height = selected.height;
|
|
249
290
|
}
|
|
250
291
|
|
|
@@ -417,17 +458,19 @@ function renderBatting() {
|
|
|
417
458
|
root.innerHTML = `<div class="empty-box">아직 등록된 씬이 없습니다. 씬을 추가해보세요.</div>`;
|
|
418
459
|
return;
|
|
419
460
|
}
|
|
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
|
-
|
|
461
|
+
state.batting_scenes.forEach((scene, index) => {
|
|
462
|
+
const row = document.createElement("article");
|
|
463
|
+
row.className = "batting-scene-row";
|
|
464
|
+
const sceneSize = normalizeImageSizeKey(scene.image_size || state.generation?.image_size || "portrait");
|
|
465
|
+
row.innerHTML = `
|
|
466
|
+
<div class="batting-scene-number">${index + 1}</div>
|
|
467
|
+
<label><span>씬 이름</span><input class="batting-scene-name" value="${escapeHtml(scene.name || `Scene ${index + 1}`)}" /></label>
|
|
468
|
+
<label><span>베이스 + 퀄리티</span><select class="batting-scene-base">${selectOptionsHtml(state.base_presets, scene.base_preset)}</select></label>
|
|
469
|
+
<label><span>캐릭터</span><select class="batting-scene-character">${selectOptionsHtml(state.character_presets, scene.character_preset)}</select></label>
|
|
470
|
+
<label><span>이미지 해상도</span><select class="batting-scene-size">${imageSizeOptionsHtml(sceneSize)}</select></label>
|
|
471
|
+
<label><span>생성 개수</span><input class="batting-scene-count" type="number" min="1" max="200" value="${Math.max(1, Number(scene.count || 2))}" /></label>
|
|
472
|
+
<button class="icon-button batting-scene-delete" type="button" title="씬 삭제">×</button>
|
|
473
|
+
`;
|
|
431
474
|
row.querySelector(".batting-scene-delete").onclick = () => deleteBattingScene(index);
|
|
432
475
|
row.querySelectorAll("input, select").forEach((node) => {
|
|
433
476
|
node.oninput = () => {
|
|
@@ -534,19 +577,47 @@ function renderPresets() {
|
|
|
534
577
|
bindAutosaveInputs(editors);
|
|
535
578
|
}
|
|
536
579
|
|
|
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
|
-
|
|
549
|
-
|
|
580
|
+
function renderSettings() {
|
|
581
|
+
const form = $("#apiForm");
|
|
582
|
+
form.innerHTML = "";
|
|
583
|
+
for (const [key, label, type] of apiFields) {
|
|
584
|
+
const wrap = document.createElement("label");
|
|
585
|
+
if (type === "select") {
|
|
586
|
+
wrap.innerHTML = `<span>${label}</span><select id="api_${key}"></select>`;
|
|
587
|
+
} else {
|
|
588
|
+
wrap.innerHTML = `<span>${label}</span><input id="api_${key}" type="${type}" />`;
|
|
589
|
+
}
|
|
590
|
+
form.appendChild(wrap);
|
|
591
|
+
const input = $(`#api_${key}`);
|
|
592
|
+
if (type === "select") {
|
|
593
|
+
input.innerHTML = (apiSelectOptions[key] || [])
|
|
594
|
+
.map(([value, optionLabel]) => `<option value="${escapeHtml(value)}">${escapeHtml(optionLabel)}</option>`)
|
|
595
|
+
.join("");
|
|
596
|
+
const saved = state.api[key] ?? "";
|
|
597
|
+
if (Array.from(input.options).some((option) => option.value === saved)) {
|
|
598
|
+
input.value = saved;
|
|
599
|
+
} else if (saved) {
|
|
600
|
+
const custom = document.createElement("option");
|
|
601
|
+
custom.value = saved;
|
|
602
|
+
custom.textContent = `${saved} (custom)`;
|
|
603
|
+
input.appendChild(custom);
|
|
604
|
+
input.value = saved;
|
|
605
|
+
}
|
|
606
|
+
} else {
|
|
607
|
+
input.value = key === "token" ? "" : state.api[key] ?? "";
|
|
608
|
+
}
|
|
609
|
+
if (key === "token") {
|
|
610
|
+
input.placeholder = state.api.token_saved
|
|
611
|
+
? "저장된 토큰을 사용합니다. 새 토큰을 입력하면 교체됩니다."
|
|
612
|
+
: "API 토큰을 입력하세요";
|
|
613
|
+
input.autocomplete = "off";
|
|
614
|
+
const help = document.createElement("small");
|
|
615
|
+
help.className = "field-help";
|
|
616
|
+
help.textContent = state.api.token_saved
|
|
617
|
+
? "보안을 위해 저장된 토큰은 다시 표시하지 않습니다. 새 토큰을 입력하고 설정 저장을 누르면 기존 토큰을 교체합니다."
|
|
618
|
+
: "토큰은 브라우저에 다시 노출하지 않고 로컬에만 저장합니다.";
|
|
619
|
+
wrap.appendChild(help);
|
|
620
|
+
}
|
|
550
621
|
if (type === "number") input.step = "any";
|
|
551
622
|
}
|
|
552
623
|
$("#mockMode").checked = !!state.api.mock_mode;
|
|
@@ -1044,11 +1115,12 @@ function addBattingScene() {
|
|
|
1044
1115
|
state.batting_scenes = state.batting_scenes || [];
|
|
1045
1116
|
const nextIndex = state.batting_scenes.length + 1;
|
|
1046
1117
|
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
|
-
|
|
1118
|
+
name: uniqueName(state.batting_scenes, `Scene ${nextIndex}`),
|
|
1119
|
+
base_preset: state.generation.base_preset || state.base_presets[0]?.name || "",
|
|
1120
|
+
character_preset: state.generation.character_preset || state.character_presets[0]?.name || "",
|
|
1121
|
+
image_size: normalizeImageSizeKey(state.generation.image_size || "portrait"),
|
|
1122
|
+
count: 2,
|
|
1123
|
+
});
|
|
1052
1124
|
renderBatting();
|
|
1053
1125
|
scheduleSave();
|
|
1054
1126
|
}
|
|
@@ -1561,11 +1633,13 @@ function bindAutosaveInputs(root = document) {
|
|
|
1561
1633
|
root.querySelectorAll("input, textarea, select").forEach((node) => {
|
|
1562
1634
|
if (node.id === "compareHistorySelect") return;
|
|
1563
1635
|
if (node.id === "deleteFilesToggle" || node.closest("#historyList")) return;
|
|
1564
|
-
if (node.id === "api_token") {
|
|
1565
|
-
node.oninput =
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1636
|
+
if (node.id === "api_token") {
|
|
1637
|
+
node.oninput = () => {
|
|
1638
|
+
setSaveState(node.value.trim() ? "새 API 토큰 입력됨" : "저장된 API 토큰 유지");
|
|
1639
|
+
};
|
|
1640
|
+
node.onchange = () => {
|
|
1641
|
+
syncEditorsToState();
|
|
1642
|
+
scheduleSave();
|
|
1569
1643
|
};
|
|
1570
1644
|
return;
|
|
1571
1645
|
}
|
|
@@ -1591,19 +1665,24 @@ function switchTab(tab) {
|
|
|
1591
1665
|
}
|
|
1592
1666
|
currentTab = tab;
|
|
1593
1667
|
$$(".nav-item").forEach((button) => button.classList.toggle("active", button.dataset.tab === tab));
|
|
1594
|
-
$$(".tab-page").forEach((page) => page.classList.toggle("active", page.id === tab));
|
|
1668
|
+
$$(".tab-page").forEach((page) => page.classList.toggle("active", page.id === tab));
|
|
1595
1669
|
$("#pageTitle").textContent = pageCopy[tab][0];
|
|
1596
1670
|
$("#pageSubtitle").textContent = pageCopy[tab][1];
|
|
1597
|
-
$("#generateButton").hidden = tab !== "generate";
|
|
1598
1671
|
updateJobActionButtons();
|
|
1599
1672
|
}
|
|
1600
1673
|
|
|
1601
1674
|
async function loadState() {
|
|
1602
1675
|
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
|
-
|
|
1676
|
+
state = data.state;
|
|
1677
|
+
state.quality_override_prompt = state.quality_override_prompt || "";
|
|
1678
|
+
state.batting_scenes = state.batting_scenes || [];
|
|
1679
|
+
state.generation = state.generation || {};
|
|
1680
|
+
state.generation.image_size = normalizeImageSizeKey(state.generation.image_size || "portrait");
|
|
1681
|
+
state.batting_scenes = state.batting_scenes.map((scene) => ({
|
|
1682
|
+
...scene,
|
|
1683
|
+
image_size: normalizeImageSizeKey(scene.image_size || state.generation.image_size),
|
|
1684
|
+
}));
|
|
1685
|
+
selectedHistoryIds = new Set(Array.from(selectedHistoryIds).filter((id) => state.history.some((item) => item.id === id)));
|
|
1607
1686
|
categoryIndex = Math.min(categoryIndex, Math.max(0, state.categories.length - 1));
|
|
1608
1687
|
baseIndex = Math.min(baseIndex, Math.max(0, state.base_presets.length - 1));
|
|
1609
1688
|
charIndex = Math.min(charIndex, Math.max(0, state.character_presets.length - 1));
|
|
@@ -1617,11 +1696,10 @@ async function loadState() {
|
|
|
1617
1696
|
|
|
1618
1697
|
function bindEvents() {
|
|
1619
1698
|
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;
|
|
1699
|
+
$$(".nav-item").forEach((button) => button.onclick = () => switchTab(button.dataset.tab));
|
|
1700
|
+
$("#refreshButton").onclick = loadState;
|
|
1701
|
+
$("#previewButton").onclick = previewPrompt;
|
|
1702
|
+
$("#generateButtonInline").onclick = startGeneration;
|
|
1625
1703
|
$("#stopGenerateButton").onclick = stopGeneration;
|
|
1626
1704
|
$("#clearFixedArtistsButton").onclick = clearFixedArtists;
|
|
1627
1705
|
$("#addBattingSceneButton").onclick = addBattingScene;
|
package/web/styles.css
CHANGED
|
@@ -319,6 +319,12 @@ label span,
|
|
|
319
319
|
font-size: 12px;
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
.field-help {
|
|
323
|
+
color: var(--muted);
|
|
324
|
+
font-size: 11px;
|
|
325
|
+
line-height: 1.45;
|
|
326
|
+
}
|
|
327
|
+
|
|
322
328
|
input,
|
|
323
329
|
select,
|
|
324
330
|
textarea {
|
|
@@ -406,7 +412,7 @@ pre {
|
|
|
406
412
|
|
|
407
413
|
.batting-scene-row {
|
|
408
414
|
display: grid;
|
|
409
|
-
grid-template-columns: 42px minmax(
|
|
415
|
+
grid-template-columns: 42px minmax(140px, 0.9fr) minmax(170px, 1fr) minmax(170px, 1fr) minmax(190px, 1fr) 100px 38px;
|
|
410
416
|
gap: 10px;
|
|
411
417
|
align-items: end;
|
|
412
418
|
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
|
|