This commit is contained in:
mike
2026-06-28 05:02:44 +02:00
parent 256a3b6ac8
commit 419f732cf6
2 changed files with 321 additions and 45 deletions

View File

@@ -2305,6 +2305,8 @@
// --- HYDRATION_START ---
const PRELOADED_IMAGES = [
"20260628_045734_image.png",
"_turntable/cg_7ec17537/views/20260628_045628_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_044252_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_043545_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_043148_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
@@ -5129,9 +5131,9 @@
"20260624_183010_0_20260624_182958_image.png",
"20260624_182958_image.png",
"20260624_174900_image.png",
"20260624_174943_dup_20260624_173115_image.png",
"20260624_175711_dup_20260624_174943_dup_20260624_173115_image.png",
"20260624_173115_image.png",
"20260624_174943_dup_20260624_173115_image.png",
"20260624_174757_image.png",
"20260624_174442_image.png",
"20260624_172923_image.nobg.png",
@@ -6923,6 +6925,8 @@
let _fsJobId = null;
let _fsJobPollTimer = null;
let availableVideos = []; // populated from /videos
let wireframeGroups = []; // grouped wireframe assets
let wireframeStandaloneImages = []; // standalone / uploaded wireframe images
let _fsActiveTab = 'swap';
let _fsSelectedPoses = new Set();
let _sbPoseFilter = ''; // live pose-name filter query (Generate tab)
@@ -11640,15 +11644,26 @@
// ---- faceswap sidebar tab ----
async function loadTemplateVideos() {
let data = null;
try {
const r = await fetch(`${API}/output/_data/videos.json`, { signal: AbortSignal.timeout(3000) });
if (r.ok) { availableVideos = (await r.json()).videos || []; return; }
if (r.ok) { data = await r.json(); }
} catch (_) {}
try {
const r = await fetch(`${API}/videos`);
if (!r.ok) return;
availableVideos = (await r.json()).videos || [];
} catch (_) { availableVideos = []; }
if (!data) {
try {
const r = await fetch(`${API}/videos`);
if (r.ok) { data = await r.json(); }
} catch (_) {}
}
if (data) {
availableVideos = data.videos || [];
wireframeGroups = data.groups || [];
wireframeStandaloneImages = data.standalone_images || [];
} else {
availableVideos = [];
wireframeGroups = [];
wireframeStandaloneImages = [];
}
}
async function lbFaceswap() {
@@ -12031,8 +12046,59 @@
// --- Video picker grid (collapsible) ---
if (!_sceneVideo || gridOpen) {
html += '<div class="sb-label">Background video</div>';
if (availableVideos.length) {
html += '<div class="sb-label">Background source library</div>';
if (wireframeGroups && wireframeGroups.length) {
html += '<div style="display:flex;flex-direction:column;gap:12px;margin-bottom:12px">';
wireframeGroups.forEach(g => {
const hasClips = g.clips && g.clips.length;
const hasFrames = g.frames && g.frames.length;
html += `
<div style="background:#141414;border:1px solid #222;border-radius:6px;padding:8px">
<div style="font-size:12px;font-weight:bold;color:#ccc;margin-bottom:6px;display:flex;align-items:center;justify-content:space-between">
<span>📹 ${escHtml(g.stem)}</span>
<span style="font-size:10px;color:#666">${g.video ? 'source video' : 'no source'}</span>
</div>`;
// Render main video if exists
if (g.video) {
html += '<div class="sb-template-grid" style="margin-bottom:6px">';
const vSafe = g.video.replace(/'/g, "\\'");
html += _tplCardHTML(g.video, _sceneVideo === g.video, `sceneSelectVideo('${vSafe}')`);
html += '</div>';
}
// Render trimmed clips if they exist
if (hasClips) {
html += '<div style="font-size:10px;color:#888;margin:4px 0 2px">✂ Trimmed Clips:</div>';
html += '<div class="sb-template-grid" style="margin-bottom:6px">';
g.clips.forEach(clip => {
const cSafe = clip.replace(/'/g, "\\'");
html += _tplCardHTML(clip, _sceneVideo === clip, `sceneSelectVideo('${cSafe}')`);
});
html += '</div>';
}
// Render extracted frames if they exist
if (hasFrames) {
html += '<div style="font-size:10px;color:#888;margin:4px 0 2px">🖼 Extracted Frames:</div>';
html += '<div style="display:flex;flex-wrap:wrap;gap:4px">';
g.frames.forEach(frame => {
const fSafe = frame.replace(/'/g, "\\'");
const isSelected = _sceneRefs[0] && _sceneRefs[0].kind === 'wireframe' && _sceneRefs[0].name === frame;
const borderStyle = isSelected ? 'border:2px solid #2563eb' : 'border:1px solid #333';
html += `
<div style="cursor:pointer;position:relative" title="${escHtml(frame)}" onclick="sceneSelectWireframeImage('${fSafe}')">
<img src="${API}/wireframe/${encodeURIComponent(frame)}" style="width:48px;height:48px;object-fit:cover;border-radius:4px;${borderStyle}">
</div>`;
});
html += '</div>';
}
html += '</div>';
});
html += '</div>';
} else if (availableVideos.length) {
html += '<div class="sb-template-grid">';
availableVideos.forEach(v => {
const vSafe = v.replace(/'/g, "\\'");
@@ -12042,6 +12108,22 @@
} else {
html += '<div style="font-size:11px;color:#555;padding:6px 0">No wireframe videos found</div>';
}
// Standalone / uploaded images section
if (wireframeStandaloneImages && wireframeStandaloneImages.length) {
html += '<div class="sb-label" style="margin-top:10px">Standalone / Uploaded Images</div>';
html += '<div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:12px;background:#141414;border:1px solid #222;border-radius:6px;padding:8px">';
wireframeStandaloneImages.forEach(img => {
const imgSafe = img.replace(/'/g, "\\'");
const isSelected = _sceneRefs[0] && _sceneRefs[0].kind === 'wireframe' && _sceneRefs[0].name === img;
const borderStyle = isSelected ? 'border:2px solid #2563eb' : 'border:1px solid #333';
html += `
<div style="cursor:pointer;position:relative" title="${escHtml(img)}" onclick="sceneSelectWireframeImage('${imgSafe}')">
<img src="${API}/wireframe/${encodeURIComponent(img)}" style="width:48px;height:48px;object-fit:cover;border-radius:4px;${borderStyle}">
</div>`;
});
html += '</div>';
}
}
// Upload alternative for the background (still image 1)
@@ -12200,6 +12282,7 @@
function _sceneThumb(ref) {
if (!ref) return '';
if (ref.kind === 'bytes') return 'data:image/png;base64,' + ref.data;
if (ref.kind === 'wireframe') return `${API}/wireframe/${encodeURIComponent(ref.name)}`;
const idx = lbNames.indexOf(ref.name);
const u = idx >= 0 ? lbUrls[idx] : (IMAGE_FOLDER + ref.name);
return isVideo(ref.name) ? posterFor(u) : u;
@@ -12286,14 +12369,23 @@
async function sceneSelectVideo(v) {
_sceneVideo = v; _sceneFrameBytes = null;
if (_sceneRefs[0] && _sceneRefs[0].kind === 'bytes') _sceneRefs[0] = null;
if (_sceneRefs[0] && (_sceneRefs[0].kind === 'bytes' || _sceneRefs[0].kind === 'wireframe')) _sceneRefs[0] = null;
_sceneGridOpen = false; // collapse the picker so the player is front-and-centre
renderSidebarScenery();
}
function sceneSelectWireframeImage(filename) {
_sceneRefs[0] = { kind: 'wireframe', name: filename };
_sceneVideo = null;
_sceneFrameBytes = null;
_sceneGridOpen = false;
_sceneRefresh();
showToast('Background set from wireframe folder', 'success');
}
function sceneReleaseFrame() {
_sceneFrameBytes = null;
if (_sceneRefs[0] && _sceneRefs[0].kind === 'bytes') _sceneRefs[0] = null;
if (_sceneRefs[0] && (_sceneRefs[0].kind === 'bytes' || _sceneRefs[0].kind === 'wireframe')) _sceneRefs[0] = null;
renderSidebarScenery(); // re-render restores the live video + slider + capture button
}
@@ -12315,8 +12407,10 @@
} else {
const d = await r.json();
_sceneFrameBytes = d.frame_b64;
_sceneRefs[0] = { kind: 'bytes', data: d.frame_b64 }; // background → slot 0
_sceneRefs[0] = { kind: 'wireframe', name: d.filename }; // background → slot 0
_sceneRefresh(); // show the frozen frame + update slot badge
// Reload template list so the new frame is instantly visible in the library
loadTemplateVideos().then(() => _sceneRefresh());
}
} catch (e) {
showToast('Frame extract error: ' + e, 'error');
@@ -12324,17 +12418,38 @@
}
}
function sceneHandleUpload(event) {
async function sceneHandleUpload(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
_sceneFrameBytes = e.target.result.split(',')[1];
_sceneRefs[0] = { kind: 'bytes', data: _sceneFrameBytes }; // background → slot 0
showToast('Uploading background image to wireframe folder…', 'info');
const fd = new FormData();
fd.append('image', file);
try {
const r = await fetch(`${API}/wireframe/upload`, {
method: 'POST',
body: fd
});
if (!r.ok) {
showToast('Upload failed: ' + await r.text(), 'error');
return;
}
const d = await r.json();
showToast('Upload successful!', 'success');
// Select this uploaded image as background
_sceneRefs[0] = { kind: 'wireframe', name: d.filename };
_sceneVideo = null;
renderSidebarScenery();
};
reader.readAsDataURL(file);
_sceneFrameBytes = null;
// Force a reload of template videos so the newly uploaded file is visible in the library
availableVideos = [];
await loadTemplateVideos();
_sceneRefresh();
} catch (e) {
showToast('Upload error: ' + e, 'error');
}
}
async function submitGenerateScenery() {
@@ -12352,16 +12467,24 @@
if (textEl) textEl.textContent = 'Submitting…';
if (prompt) savePromptHistory(prompt);
// slot0 → scene_bytes: bytes directly, or fetch a file-as-background to bytes.
let sceneBytes;
try {
sceneBytes = (bg.kind === 'bytes') ? bg.data : await _fetchAsBase64(_sceneUrlForName(bg.name));
} catch (e) {
showToast('Could not read background image: ' + e, 'error');
if (btn) btn.disabled = false; if (statusEl) statusEl.style.display = 'none'; return;
let sceneBytes = null;
let sceneImage = null;
if (bg.kind === 'bytes') {
sceneBytes = bg.data;
} else if (bg.kind === 'wireframe') {
sceneImage = bg.name;
} else {
try {
sceneBytes = await _fetchAsBase64(_sceneUrlForName(bg.name));
} catch (e) {
showToast('Could not read background image: ' + e, 'error');
if (btn) btn.disabled = false; if (statusEl) statusEl.style.display = 'none'; return;
}
}
const payload = {
model_filename: person.name, // image2 (Picture 2) — person
scene_bytes: sceneBytes, // image1 (Picture 1) — background
scene_image: sceneImage, // image1 (Picture 1) — direct wireframe image filename
scene_video: null,
scene_time: 0,
extra_filename: (extra && extra.kind === 'file') ? extra.name : null, // image3 (Picture 3)