auto-save 2026-05-14 06:27 (~4)
This commit is contained in:
@@ -1,19 +1,5 @@
|
||||
{
|
||||
"entries": [
|
||||
{
|
||||
"files_changed": 2,
|
||||
"hash": "e6b8615",
|
||||
"message": "auto-save 2026-05-12 17:23 (~2)",
|
||||
"ts": "2026-05-12T17:23:21+08:00",
|
||||
"type": "commit"
|
||||
},
|
||||
{
|
||||
"files_changed": 6,
|
||||
"hash": "6a9abea",
|
||||
"message": "auto-save 2026-05-12 17:28 (~6)",
|
||||
"ts": "2026-05-12T17:28:54+08:00",
|
||||
"type": "commit"
|
||||
},
|
||||
{
|
||||
"files_changed": 1,
|
||||
"hash": "f3ec026",
|
||||
@@ -3351,6 +3337,19 @@
|
||||
"type": "session-heartbeat",
|
||||
"message": "Codex 会话活跃 · 最近命令:codex · 2 项未提交变更 · 最近提交:auto-save 2026-05-14 06:16 (~5)",
|
||||
"files_changed": 2
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-14T06:22:32+08:00",
|
||||
"type": "commit",
|
||||
"message": "auto-save 2026-05-14 06:22 (~2)",
|
||||
"hash": "2d36ada",
|
||||
"files_changed": 2
|
||||
},
|
||||
{
|
||||
"ts": "2026-05-13T22:23:14Z",
|
||||
"type": "session-heartbeat",
|
||||
"message": "Claude 会话活跃 · 最近命令:claude · 1 项未提交变更 · 最近提交:auto-save 2026-05-14 06:22 (~2)",
|
||||
"files_changed": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
33
api/main.py
33
api/main.py
@@ -448,7 +448,7 @@ def run(cmd: list[str], cwd: Path | None = None) -> str:
|
||||
# ---- 启发式选帧工具 ----
|
||||
import imagehash
|
||||
import numpy as np
|
||||
from PIL import Image, ImageEnhance, ImageFilter, ImageOps
|
||||
from PIL import Image, ImageChops, ImageEnhance, ImageFilter, ImageOps
|
||||
|
||||
|
||||
def _sharpness_from_gray(g: np.ndarray) -> float:
|
||||
@@ -592,6 +592,7 @@ def _normalize_asset_image(
|
||||
size: AssetSize,
|
||||
background: AssetBackground = "white",
|
||||
square: bool = False,
|
||||
fill_subject: bool = False,
|
||||
) -> tuple[int, int]:
|
||||
import io as _io
|
||||
target_w, target_h = _asset_target_size(source_path, size, square=square)
|
||||
@@ -599,7 +600,25 @@ def _normalize_asset_image(
|
||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with Image.open(_io.BytesIO(img_bytes)) as raw:
|
||||
img = raw.convert("RGB")
|
||||
img.thumbnail((target_w, target_h), Image.Resampling.LANCZOS)
|
||||
if fill_subject:
|
||||
diff = ImageChops.difference(img, Image.new("RGB", img.size, bg))
|
||||
mask = diff.convert("L").point(lambda px: 255 if px > 18 else 0)
|
||||
bbox = mask.getbbox()
|
||||
if bbox:
|
||||
left, top, right, bottom = bbox
|
||||
pad_x = round((right - left) * 0.06)
|
||||
pad_y = round((bottom - top) * 0.06)
|
||||
img = img.crop((
|
||||
max(0, left - pad_x),
|
||||
max(0, top - pad_y),
|
||||
min(img.width, right + pad_x),
|
||||
min(img.height, bottom + pad_y),
|
||||
))
|
||||
max_w = max(1, round(target_w * 0.92))
|
||||
max_h = max(1, round(target_h * 0.94))
|
||||
img.thumbnail((max_w, max_h), Image.Resampling.LANCZOS)
|
||||
else:
|
||||
img.thumbnail((target_w, target_h), Image.Resampling.LANCZOS)
|
||||
canvas = Image.new("RGB", (target_w, target_h), bg)
|
||||
canvas.paste(img, ((target_w - img.width) // 2, (target_h - img.height) // 2))
|
||||
canvas.save(out_path, "JPEG", quality=95)
|
||||
@@ -699,6 +718,8 @@ SUBJECT_VIEW_LABELS: dict[str, str] = {
|
||||
"back": "背面",
|
||||
"left": "左侧",
|
||||
"right": "右侧",
|
||||
"three_quarter_left": "左前 45°",
|
||||
"three_quarter_right": "右前 45°",
|
||||
"side": "侧面",
|
||||
"side_walk": "侧面走路",
|
||||
"top": "顶部视角",
|
||||
@@ -727,10 +748,10 @@ def _subject_view_labels(kind: SubjectKind, requested: list[str] | None = None)
|
||||
return [
|
||||
("front", "正面站立"),
|
||||
("back", "背面站立"),
|
||||
("side", "侧面站立"),
|
||||
("side_walk", "侧面走路"),
|
||||
("expression_neutral", "中性表情"),
|
||||
("expression_relaxed", "放松表情"),
|
||||
("left", "左侧站立"),
|
||||
("right", "右侧站立"),
|
||||
("three_quarter_left", "左前 45° 站立"),
|
||||
("three_quarter_right", "右前 45° 站立"),
|
||||
]
|
||||
return [
|
||||
("front", "正面"),
|
||||
|
||||
@@ -36,15 +36,10 @@ const OBJECT_VIEW_OPTIONS = [
|
||||
const LIVING_VIEW_OPTIONS = [
|
||||
["front", "正面"],
|
||||
["back", "背面"],
|
||||
["side", "侧面"],
|
||||
["side_walk", "走路"],
|
||||
["expression_happy", "喜"],
|
||||
["expression_angry", "怒"],
|
||||
["expression_sad", "哀"],
|
||||
["expression_relaxed", "乐/放松"],
|
||||
["action_sit", "坐"],
|
||||
["action_hold", "手持"],
|
||||
["action_use", "使用"],
|
||||
["left", "左侧"],
|
||||
["right", "右侧"],
|
||||
["three_quarter_left", "左前 45°"],
|
||||
["three_quarter_right", "右前 45°"],
|
||||
]
|
||||
|
||||
type LightboxTab = "clean" | "scene" | "subject" | "review"
|
||||
@@ -266,7 +261,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
views,
|
||||
})
|
||||
onJobUpdate?.(updated)
|
||||
toast.success(`统一主体资产包已生成 · ${views.length} 张 · ${subjectReferenceFrameIndices.length} 帧参考`)
|
||||
toast.success(`统一主体重绘完成 · ${views.length} 张 · ${subjectReferenceFrameIndices.length} 帧参考`)
|
||||
} catch (e) {
|
||||
toast.error("主体资产包生成失败:" + (e instanceof Error ? e.message : String(e)))
|
||||
} finally {
|
||||
@@ -924,7 +919,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
<span className="text-[9.5px] text-white/35 font-normal ml-auto">→ {subjectReferenceLabel}</span>
|
||||
</div>
|
||||
<div className="mb-2 rounded-md border border-white/10 bg-white/[0.035] px-2.5 py-1.5 text-[10.5px] leading-relaxed text-white/45">
|
||||
当前工作流只保留一个主体。多张关键帧用于补齐这个主体的角度、动作和表情;场景图会按每张关键帧单独生成。
|
||||
当前工作流只保留一个主体。多张关键帧用于重绘这个主体的六张标准站立资产图;场景图会按每张关键帧单独生成。
|
||||
</div>
|
||||
|
||||
{!hasUnifiedSubject && (
|
||||
@@ -1053,7 +1048,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
|
||||
<div className="mt-2 rounded-md border border-violet-300/15 bg-violet-500/[0.08] p-2">
|
||||
<div className="mb-2 flex items-center justify-between gap-2">
|
||||
<div className="text-[11px] font-semibold text-white/90">统一主体资产包</div>
|
||||
<div className="text-[11px] font-semibold text-white/90">统一主体重绘图</div>
|
||||
<span className="text-[9.5px] font-mono text-white/35">
|
||||
{subjectReferenceLabel}
|
||||
</span>
|
||||
@@ -1084,7 +1079,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
onClick={() => handleGenerateSubjectPackage(e.id, frameIndex)}
|
||||
disabled={isSubjectGenerating || activeViews.length === 0}
|
||||
className="rounded bg-violet-500/70 px-1.5 py-1 text-[10px] font-medium text-white transition hover:bg-violet-400 disabled:cursor-wait disabled:opacity-45 inline-flex items-center justify-center gap-1"
|
||||
title="用多张关键帧参考生成同一个主体的多视角 / 动作 / 表情资产"
|
||||
title="用多张关键帧参考重绘同一个主体的六张标准站立图"
|
||||
>
|
||||
{isSubjectGenerating ? <Loader2 className="h-3 w-3 animate-spin" /> : <Wand2 className="h-3 w-3" />}
|
||||
{isSubjectGenerating ? "生成" : "生成"}
|
||||
@@ -1150,7 +1145,7 @@ export function FrameLightbox({ jobId, frames, activeIndex, selected, onClose, o
|
||||
)}
|
||||
|
||||
<div className="mt-1.5 text-[10px] text-white/35 leading-relaxed">
|
||||
主体候选来自识别结果;确认唯一主体、类型和视角后,使用参考帧生成统一主体资产包。
|
||||
主体候选来自识别结果;确认唯一主体和类型后,使用参考帧重新绘制纯背景、占满画面的主体图。
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1561,7 +1561,7 @@ export function VisualLabNode({ data, selected }: any) {
|
||||
onClick={(e) => { e.stopPropagation(); openFirstFrame() }}
|
||||
disabled={!job || frames.length === 0}
|
||||
className="min-h-14 rounded-md border border-white/10 px-2 py-2 text-left transition hover:border-violet-300/50 hover:bg-violet-400/10 disabled:opacity-35"
|
||||
title="用多张关键帧生成统一主体的多视角 / 动作 / 表情资产包"
|
||||
title="用多张关键帧参考重绘统一主体的六张标准站立图"
|
||||
>
|
||||
<div className="mb-1 flex items-center gap-1 text-[var(--text-strong)] text-[12px] font-semibold">
|
||||
<Package className="h-3 w-3 text-violet-300" />
|
||||
|
||||
Reference in New Issue
Block a user