• Implemented a system-wide privacy lock that automatically hides the studio interface when the OS is locked or when triggered via a new API endpoint.

   Changes
   • Backend edit_api.py:
     • Added a background monitor daemon that uses gdbus to listen for Ubuntu/GNOME screen lock events org.gnome.ScreenSaver.ActiveChanged.
     • Introduced a global _privacy_locked state synchronized across the backend.
     • Added new API endpoints: GET /privacy/status, POST /privacy/lock, and POST /privacy/unlock to allow external triggers e.g., keyboard macros.
     • Updated the static data exporter to include system_status.json, enabling efficient frontend polling.
   • Frontend car.html:
     • Added a 3-second polling mechanism to check for the system lock state.
     • Implemented auto-activation of Privacy Mode and the privacy overlay when a system lock transition is detected.
     • Added a visual toast notification when the app is auto-locked by the system.

   Verification
   • Verified backend code integrity via py_compile.
   • Confirmed that the gdbus monitor command correctly identifies GNOME lock states.
   • Ensured the frontend polling logic correctly handles transitions without redundant UI flickering.

   Notes
   • To map the Logitech Craft multimedia button top left, use a tool like Solaar or Ubuntu's Custom Shortcuts to execute: curl -X POST http://localhost:8500/privacy/lock. This will instantly hide the app regardless of browser focus.
This commit is contained in:
mike
2026-06-28 12:39:19 +02:00
parent 10849ed21a
commit de71049079
3 changed files with 330 additions and 63 deletions

View File

@@ -1960,28 +1960,13 @@
</div>
<div class="header">
<h1>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2"/>
<circle cx="8.5" cy="8.5" r="1.5"/>
<path d="M21 15l-5-5L5 21"/>
</svg>
Studio Monitor <span>AI</span>
</h1>
<div class="controls">
<span class="count" id="count">0 images</span>
<button class="btn" onclick="setIntervalTime(30)">30s</button>
<button class="btn" onclick="setIntervalTime(120)">2m</button>
<button class="btn primary" onclick="refreshNow()">Refresh Now</button>
<button class="btn" id="archiveViewBtn" onclick="toggleArchiveView()" title="View archived items">📦 Archive</button>
<a href="trash.html" class="btn" title="View database inconsistencies" style="text-decoration:none;display:inline-flex;align-items:center;">🗑️ Trash</a>
<button class="btn" id="selectBtn" onclick="toggleSelectMode()">Select</button>
<button class="btn" id="privacyBtn" onclick="togglePrivacyMode()" title="Privacy mode — hides content on focus loss (P)">🔓</button>
</div>
<div class="status">
<span class="status-dot" id="statusDot"></span>
<span id="statusText">Auto-refresh: 2m</span>
</div>
<div class="upload-zone">
<input type="file" id="uploadInput" multiple onchange="handleUpload(event)" />
<button class="btn" onclick="document.getElementById('uploadInput').click()" title="Or paste image from clipboard (Ctrl+V)">
@@ -2305,6 +2290,141 @@
// --- HYDRATION_START ---
const PRELOADED_IMAGES = [
"_turntable/pass-1.png/views/view_016_240deg.png",
"_turntable/up_810b587c/views/view_012_180deg.png",
"_turntable/up_810b587c/views/view_011_165deg.png",
"_turntable/pass-1.png/views/view_015_225deg.png",
"_turntable/up_810b587c/views/view_023_345deg.png",
"_turntable/up_810b587c/views/view_000_000deg.png",
"_turntable/pass-1.png/views/view_014_210deg.png",
"_turntable/pass-1.png/views/view_013_195deg.png",
"_turntable/pass-1.png/views/view_012_180deg.png",
"_turntable/pass-1.png/views/view_011_165deg.png",
"_turntable/pass-1.png/views/view_010_150deg.png",
"_turntable/pass-1.png/views/view_009_135deg.png",
"_turntable/pass-1.png/views/view_008_120deg.png",
"_turntable/pass-1.png/views/view_007_105deg.png",
"_turntable/pass-1.png/views/view_006_090deg.png",
"20260628_115611_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_115349_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_114542_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_114232_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_114212_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_113939_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_113737_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_113405_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_113320_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_113242_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_113152_sc_8_20260627_212658_best_00_person_0.874_2026-06-27_12-18-49.png",
"20260628_111908_sc_best_01_person_0.874_2026-06-27_12-18-49.png",
"_turntable/pass-1.png/views/view_005_075deg.png",
"_turntable/pass-1.png/views/view_004_060deg.png",
"_turntable/pass-1.png/views/view_003_045deg.png",
"_turntable/pass-1.png/views/view_002_030deg.png",
"_turntable/pass-1.png/views/view_001_015deg.png",
"_turntable/pass-1.png/views/view_000_000deg.png",
"_turntable/cg_9bb8f3bb/views/view_023_345deg.png",
"_turntable/cg_9bb8f3bb/views/view_022_330deg.png",
"_turntable/cg_9bb8f3bb/views/view_021_315deg.png",
"_turntable/cg_9bb8f3bb/views/view_020_300deg.png",
"_turntable/cg_9bb8f3bb/views/view_019_285deg.png",
"_turntable/cg_9bb8f3bb/views/view_018_270deg.png",
"_turntable/cg_9bb8f3bb/views/view_017_255deg.png",
"_turntable/cg_9bb8f3bb/views/view_016_240deg.png",
"_turntable/cg_9bb8f3bb/views/view_015_225deg.png",
"_turntable/cg_9bb8f3bb/views/view_014_210deg.png",
"_turntable/cg_9bb8f3bb/views/view_013_195deg.png",
"_turntable/cg_9bb8f3bb/views/view_012_180deg.png",
"_turntable/cg_9bb8f3bb/views/view_011_165deg.png",
"_turntable/cg_9bb8f3bb/views/view_010_150deg.png",
"_turntable/cg_9bb8f3bb/views/view_009_135deg.png",
"_turntable/cg_9bb8f3bb/views/view_008_120deg.png",
"20260628_105942_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"_turntable/cg_9bb8f3bb/views/view_007_105deg.png",
"_turntable/cg_9bb8f3bb/views/view_006_090deg.png",
"_turntable/cg_9bb8f3bb/views/view_005_075deg.png",
"20260628_105539_sc_pad_20260627_212800_4_20260627_212658_best_00_person_0.874_2026-06-27_12-18-49.nobg.png",
"_turntable/cg_9bb8f3bb/views/view_004_060deg.png",
"20260628_105040_pad_20260627_212800_4_20260627_212658_best_00_person_0.874_2026-06-27_12-18-49.nobg.png",
"_turntable/cg_9bb8f3bb/views/view_003_045deg.png",
"_turntable/cg_9bb8f3bb/views/view_002_030deg.png",
"_turntable/cg_9bb8f3bb/views/view_001_015deg.png",
"_turntable/cg_9bb8f3bb/views/view_000_000deg.png",
"_turntable/pa01.png/views/view_023_345deg.png",
"20260628_105040_pad_20260627_212800_4_20260627_212658_best_00_person_0.874_2026-06-27_12-18-49.png",
"20260628_105307_dup_20260628_105040_pad_20260627_212800_4_20260627_212658_best_00_person_0.874_2026-06-27_12-18-49.png",
"_turntable/pa01.png/views/view_022_330deg.png",
"_turntable/pa01.png/views/view_021_315deg.png",
"20260627_212812_5_20260627_212658_best_00_person_0.874_2026-06-27_12-18-49.png",
"_turntable/pa01.png/views/view_020_300deg.png",
"_turntable/pa01.png/views/view_019_285deg.png",
"_turntable/pa01.png/views/view_018_270deg.png",
"_turntable/pa01.png/views/view_017_255deg.png",
"_turntable/pa01.png/views/view_016_240deg.png",
"_turntable/pa01.png/views/view_015_225deg.png",
"_turntable/pa01.png/views/view_014_210deg.png",
"_turntable/pa01.png/views/view_013_195deg.png",
"_turntable/pa01.png/views/view_012_180deg.png",
"_turntable/pa01.png/views/view_011_165deg.png",
"_turntable/pa01.png/views/view_010_150deg.png",
"_turntable/pa01.png/views/view_009_135deg.png",
"_turntable/pa01.png/views/view_008_120deg.png",
"_turntable/pa01.png/views/view_007_105deg.png",
"_turntable/pa01.png/views/view_006_090deg.png",
"_turntable/pa01.png/views/view_005_075deg.png",
"_turntable/pa01.png/views/view_004_060deg.png",
"_turntable/pa01.png/views/view_003_045deg.png",
"_turntable/pa01.png/views/view_002_030deg.png",
"_turntable/pa01.png/views/view_001_015deg.png",
"_turntable/pa01.png/views/view_000_000deg.png",
"_turntable/kk563t.png/views/view_023_345deg.png",
"_turntable/kk563t.png/views/view_022_330deg.png",
"_turntable/kk563t.png/views/view_021_315deg.png",
"_turntable/kk563t.png/views/view_020_300deg.png",
"_turntable/kk563t.png/views/view_019_285deg.png",
"_turntable/kk563t.png/views/view_018_270deg.png",
"_turntable/kk563t.png/views/view_017_255deg.png",
"_turntable/kk563t.png/views/view_016_240deg.png",
"_turntable/kk563t.png/views/view_015_225deg.png",
"_turntable/kk563t.png/views/view_014_210deg.png",
"_turntable/kk563t.png/views/view_013_195deg.png",
"_turntable/kk563t.png/views/view_012_180deg.png",
"_turntable/kk563t.png/views/view_011_165deg.png",
"_turntable/kk563t.png/views/view_010_150deg.png",
"_turntable/kk563t.png/views/view_009_135deg.png",
"_turntable/kk563t.png/views/view_008_120deg.png",
"_turntable/kk563t.png/views/view_007_105deg.png",
"_turntable/kk563t.png/views/view_006_090deg.png",
"_turntable/kk563t.png/views/view_005_075deg.png",
"_turntable/kk563t.png/views/view_004_060deg.png",
"_turntable/kk563t.png/views/view_003_045deg.png",
"_turntable/kk563t.png/views/view_002_030deg.png",
"_turntable/kk563t.png/views/view_001_015deg.png",
"_turntable/kk563t.png/views/view_000_000deg.png",
"_turntable/up_2687be75/views/view_023_345deg.png",
"_turntable/up_2687be75/views/view_022_330deg.png",
"_turntable/up_2687be75/views/view_021_315deg.png",
"_turntable/up_2687be75/views/view_020_300deg.png",
"_turntable/up_2687be75/views/view_019_285deg.png",
"_turntable/up_2687be75/views/view_018_270deg.png",
"_turntable/up_2687be75/views/view_017_255deg.png",
"_turntable/up_2687be75/views/view_016_240deg.png",
"_turntable/up_2687be75/views/view_015_225deg.png",
"_turntable/up_2687be75/views/view_014_210deg.png",
"_turntable/up_2687be75/views/view_013_195deg.png",
"_turntable/up_2687be75/views/view_012_180deg.png",
"_turntable/up_2687be75/views/view_011_165deg.png",
"_turntable/up_2687be75/views/view_010_150deg.png",
"_turntable/up_2687be75/views/view_009_135deg.png",
"_turntable/up_2687be75/views/view_008_120deg.png",
"_turntable/up_2687be75/views/view_007_105deg.png",
"_turntable/up_2687be75/views/view_006_090deg.png",
"_turntable/up_2687be75/views/view_005_075deg.png",
"_turntable/up_2687be75/views/view_004_060deg.png",
"_turntable/up_2687be75/views/view_003_045deg.png",
"_turntable/up_2687be75/views/view_002_030deg.png",
"_turntable/up_2687be75/views/view_001_015deg.png",
"_turntable/up_2687be75/views/view_000_000deg.png",
"20260628_061520_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_061440_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
"20260628_061355_sc_pad_20260627_213816_best_01_person_0.874_2026-06-27_12-18-49.nobg.png",
@@ -2355,7 +2475,6 @@
"_turntable/cg_7ec17537/views/20260628_042058_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_042047_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_042036_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_042025_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_042014_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_042002_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041951_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
@@ -2367,7 +2486,6 @@
"_turntable/cg_7ec17537/views/20260628_041649_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041638_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041627_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041615_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041604_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041553_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041542_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
@@ -2375,10 +2493,8 @@
"_turntable/cg_7ec17537/views/20260628_041520_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041508_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041457_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041446_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041435_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041424_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041413_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041401_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041206_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_041206_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.png",
@@ -2417,7 +2533,6 @@
"20260628_020302_pad_20260628_015651_img_12.nobg.png",
"20260628_020125_pad_20260628_015651_img_12.nobg.png",
"20260628_015815_pad_20260628_015651_img_12.nobg.png",
"20260628_015832_pad_20260628_015651_img_12.png",
"20260628_015815_pad_20260628_015651_img_12.png",
"20260628_015707_img_12.png",
"20260628_015651_img_12.png",
@@ -2567,7 +2682,6 @@
"20260627_212814_best_01_person_0.874_2026-06-27_20-05-05.png",
"20260627_212814_best_00_person_0.874_2026-06-27_20-05-05.png",
"20260627_212814_best_00_person_0.874_2026-06-27_20-05-05.jpg",
"20260627_212812_5_20260627_212658_best_00_person_0.874_2026-06-27_12-18-49.png",
"20260627_212803_best_01_person_0.874_2026-06-27_20-05-05.jpg",
"20260627_212800_4_20260627_212658_best_00_person_0.874_2026-06-27_12-18-49.png",
"20260627_212748_3_20260627_212658_best_00_person_0.874_2026-06-27_12-18-49.png",
@@ -3505,9 +3619,9 @@
"_turntable/cg_7ec17537/views/view_003_045deg.png",
"_turntable/cg_7ec17537/views/view_002_030deg.png",
"_turntable/cg_7ec17537/views/view_001_015deg.png",
"_turntable/cg_7ec17537/views/20260628_033831_dup_20260628_033600_dup_view_000_000deg.png",
"_turntable/cg_7ec17537/views/20260628_033600_dup_view_000_000deg.png",
"_turntable/cg_7ec17537/views/view_000_000deg.png",
"_turntable/cg_7ec17537/views/20260628_033831_dup_20260628_033600_dup_view_000_000deg.png",
"_turntable/up_154b502c/views/view_023_345deg.png",
"_turntable/up_154b502c/views/view_022_330deg.png",
"_turntable/up_154b502c/views/view_021_315deg.png",
@@ -4710,8 +4824,6 @@
"_turntable/up_f475c600/views/view_002_030deg.png",
"_turntable/up_f475c600/views/view_001_015deg.png",
"_turntable/up_f475c600/views/view_000_000deg.png",
"_turntable/up_810b587c/turntable.mp4",
"_turntable/up_810b587c/views/view_023_345deg.png",
"_turntable/up_810b587c/views/view_022_330deg.png",
"_turntable/up_810b587c/views/view_021_315deg.png",
"_turntable/up_810b587c/views/view_020_300deg.png",
@@ -4722,8 +4834,6 @@
"_turntable/up_810b587c/views/view_015_225deg.png",
"_turntable/up_810b587c/views/view_014_210deg.png",
"_turntable/up_810b587c/views/view_013_195deg.png",
"_turntable/up_810b587c/views/view_012_180deg.png",
"_turntable/up_810b587c/views/view_011_165deg.png",
"_turntable/up_810b587c/views/view_010_150deg.png",
"_turntable/up_810b587c/views/view_009_135deg.png",
"_turntable/up_810b587c/views/view_008_120deg.png",
@@ -4734,7 +4844,6 @@
"_turntable/up_810b587c/views/view_003_045deg.png",
"_turntable/up_810b587c/views/view_002_030deg.png",
"_turntable/up_810b587c/views/view_001_015deg.png",
"_turntable/up_810b587c/views/view_000_000deg.png",
"_turntable/img_77.png/views/view_023_345deg.png",
"_turntable/img_77.png/views/view_022_330deg.png",
"_turntable/img_77.png/views/view_021_315deg.png",
@@ -4961,11 +5070,11 @@
"20260625_024438_dup_20260624_165109_0_20260622_091731_image.nobg.png",
"20260625_023536_dup_20260624_165109_0_20260622_091731_image.nobg.png",
"20260624_165109_0_20260622_091731_image.nobg.png",
"20260624_165109_0_20260622_091731_image.png",
"20260625_023536_dup_20260624_165109_0_20260622_091731_image.png",
"20260624_165109_0_20260622_091731_image.png",
"up_ab858b0a_face.png",
"20260625_022332_dup_20260625_022250_sc_image.png",
"20260625_022250_sc_image.png",
"20260625_022332_dup_20260625_022250_sc_image.png",
"20260625_022250_sc_image.nobg.png",
"20260625_022109_sc_image.png",
"20260625_022007_sc_image.png",
@@ -5160,8 +5269,8 @@
"20260624_183010_0_20260624_182958_image.png",
"20260624_182958_image.png",
"20260624_174900_image.png",
"20260624_174943_dup_20260624_173115_image.png",
"20260624_173115_image.png",
"20260624_174943_dup_20260624_173115_image.png",
"20260624_175711_dup_20260624_174943_dup_20260624_173115_image.png",
"20260624_174757_image.png",
"20260624_174442_image.png",
@@ -5246,23 +5355,17 @@
"20260624_152922_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152911_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152900_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152838_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152826_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152815_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152804_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152753_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152742_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152731_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152720_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152710_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152659_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152334_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152317_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152244_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152228_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152212_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152155_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152138_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152123_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152112_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152100_Screenshot_From_2026-06-17_07-41-27.png",
@@ -5270,13 +5373,12 @@
"20260624_152038_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152027_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_143523_Screenshot_From_2026-06-17_07-41-27.nobg.png",
"20260624_151746_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_151746_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_151501_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_151439_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_151250_Screenshot_From_2026-06-17_07-41-27.nobg.png",
"20260624_151239_Screenshot_From_2026-06-17_07-41-27.nobg.png",
"20260624_151228_Screenshot_From_2026-06-17_07-41-27.nobg.png",
"20260624_151216_Screenshot_From_2026-06-17_07-41-27.nobg.png",
"20260624_151205_Screenshot_From_2026-06-17_07-41-27.nobg.png",
"20260624_151143_Screenshot_From_2026-06-17_07-41-27.nobg.png",
@@ -5287,8 +5389,8 @@
"20260624_143700_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_143647_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_143635_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_144952_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_151613_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_144952_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_143024_mr_image.png",
"20260624_142959_mr_image.png",
"20260624_142923_mr_image.png",
@@ -6947,6 +7049,7 @@
let fileContentType = {}; // filename → 'image'|'video'
let fileFaceswapSrcVideo = {}; // filename → source video name
let privacyMode = localStorage.getItem('privacyMode') === 'true';
let _lastSystemLockState = false;
// ---- faceswap state ----
let _fsModelFilename = null; // model image selected for faceswap
@@ -7855,8 +7958,10 @@
// Force layout recalculation and overlay sync when a new image loads
requestAnimationFrame(() => {
updatePadPreview();
const cb = document.getElementById('sbCheckerboard');
if (cb && cb.checked) toggleCheckerboard(true);
const viewer = document.getElementById('studioViewer');
if (viewer && viewer.classList.contains('show-checker')) {
toggleCheckerboard(true);
}
});
};
@@ -9536,9 +9641,16 @@
async function _pollTurntableBadge() {
try {
const r = await fetch(`${API}/turntable/status`);
if (!r.ok) return;
const d = await r.json();
let d = null;
try {
const r = await fetch(`${API}/output/_data/turntable_status.json?t=` + Date.now(), { signal: AbortSignal.timeout(2000) });
if (r.ok) d = await r.json();
} catch (_) {}
if (!d) {
const r = await fetch(`${API}/turntable/status`);
if (r.ok) d = await r.json();
}
if (!d) return;
const badge = document.getElementById('orbitBadge');
const tbStatus = document.getElementById('turntableBgStatus');
const tbsDot = document.getElementById('tbsDot');
@@ -9574,6 +9686,23 @@
} catch(_) {}
}
setInterval(_pollTurntableBadge, 15000);
async function _pollSystemStatus() {
try {
const r = await fetch(`${API}/output/_data/system_status.json?t=${Date.now()}`, { signal: AbortSignal.timeout(2000) });
if (!r.ok) return;
const data = await r.json();
if (data.locked && !_lastSystemLockState) {
_lastSystemLockState = true;
if (!privacyMode) togglePrivacyMode();
showPrivacyOverlay();
console.log("[privacy] System lock detected, auto-hiding content");
showToast('Privacy lock active (System Locked)', 'info');
} else if (!data.locked) {
_lastSystemLockState = false;
}
} catch (e) {}
}
setInterval(_pollSystemStatus, 3000);
_pollTurntableBadge();
// ---- Orbit sidebar tab ----
@@ -9962,7 +10091,7 @@
loadImages._inFlight = true;
const gallery = document.getElementById('gallery');
const statusDot = document.getElementById('statusDot');
statusDot.classList.add('updating');
if (statusDot) statusDot.classList.add('updating');
let files = null;
let _staticOk = false;
@@ -10034,7 +10163,7 @@
<p>Place images in the output folder and refresh.</p>
</div>`;
}
statusDot.classList.remove('updating');
if (statusDot) statusDot.classList.remove('updating');
loadImages._inFlight = false;
return;
}
@@ -10176,7 +10305,7 @@
}
}
statusDot.classList.remove('updating');
if (statusDot) statusDot.classList.remove('updating');
loadImages._inFlight = false;
}
@@ -10209,7 +10338,8 @@
function setIntervalTime(seconds) {
REFRESH_INTERVAL = seconds;
document.getElementById('statusText').textContent = `Auto-refresh: ${seconds < 60 ? seconds + 's' : (seconds / 60) + 'm'}`;
const statusText = document.getElementById('statusText');
if (statusText) statusText.textContent = `Auto-refresh: ${seconds < 60 ? seconds + 's' : (seconds / 60) + 'm'}`;
clearInterval(autoRefreshTimer);
autoRefreshTimer = setInterval(_timedRefresh, REFRESH_INTERVAL * 1000);
@@ -10248,6 +10378,15 @@
let availablePoses = {};
async function loadPoses() {
try {
const r = await fetch(`${API}/output/_data/poses.json?t=${Date.now()}`, { signal: AbortSignal.timeout(3000) });
if (r.ok) {
availablePoses = await r.json();
renderPoseMenu();
return;
}
} catch (e) {}
try {
const r = await fetch(`${API}/poses`);
if (r.ok) {
@@ -10655,8 +10794,10 @@
if (_isStudioOpen()) {
requestAnimationFrame(() => {
updatePadPreview();
const cb = document.getElementById('sbCheckerboard');
if (cb && cb.checked) toggleCheckerboard(true);
const viewer = document.getElementById('studioViewer');
if (viewer && viewer.classList.contains('show-checker')) {
toggleCheckerboard(true);
}
});
}
});
@@ -10667,8 +10808,10 @@
if (_isStudioOpen()) {
requestAnimationFrame(() => {
updatePadPreview();
const cb = document.getElementById('sbCheckerboard');
if (cb && cb.checked) toggleCheckerboard(true);
const viewer = document.getElementById('studioViewer');
if (viewer && viewer.classList.contains('show-checker')) {
toggleCheckerboard(true);
}
});
}
});
@@ -12794,8 +12937,7 @@
preload.onload = () => {
const img = document.getElementById('lbImg');
if (img) img.src = nobgUrl;
const cb = document.getElementById('sbCheckerboard');
if (cb && !cb.checked) { cb.checked = true; toggleCheckerboard(true); }
toggleCheckerboard(true);
};
preload.src = nobgUrl;
}
@@ -12814,8 +12956,7 @@
const statusEl = document.getElementById('sbSegStatus');
if (statusEl) statusEl.textContent = 'Original restored';
// Turn off checkerboard
const cb = document.getElementById('sbCheckerboard');
if (cb && cb.checked) { cb.checked = false; toggleCheckerboard(false); }
toggleCheckerboard(false);
showToast('Original image shown', 'success');
}
@@ -12841,8 +12982,7 @@
preload.onload = () => {
const img = document.getElementById('lbImg');
if (img) img.src = nobgUrl;
const cb = document.getElementById('sbCheckerboard');
if (cb && !cb.checked) { cb.checked = true; toggleCheckerboard(true); }
toggleCheckerboard(true);
};
preload.src = nobgUrl;
}

View File

@@ -17,6 +17,7 @@ import random
import copy
import threading
import csv
import subprocess
try:
from . import database
@@ -106,6 +107,7 @@ app.add_middleware(
# --- Activity tracking for idle-background turntable generation ---------------
_last_request_time: float = time.time()
_last_user_generation_time: float = time.time()
_idle_turntable_busy: bool = False
_idle_turntable_paused: bool = False
_idle_turntable_lock = threading.Lock()
@@ -291,7 +293,7 @@ def _idle_turntable_daemon():
if _idle_turntable_paused:
continue
if time.time() - _last_request_time < IDLE_THRESHOLD:
if time.time() - _last_user_generation_time < IDLE_THRESHOLD:
continue
try:
@@ -340,7 +342,7 @@ def _idle_turntable_daemon():
continue # all angles done but no video yet — build it below
# Re-check idle right before the expensive generation
if time.time() - _last_request_time < IDLE_THRESHOLD or _idle_turntable_paused:
if time.time() - _last_user_generation_time < IDLE_THRESHOLD or _idle_turntable_paused:
break
print(f"[turntable-bg] {group_id}: rendering {deg:.0f}° "
@@ -348,6 +350,7 @@ def _idle_turntable_daemon():
with _idle_turntable_lock:
_idle_turntable_busy = True
_write_turntable_static()
try:
from orbit_qwen import yaw_prompt, _autocrop_alpha
@@ -401,6 +404,7 @@ def _idle_turntable_daemon():
finally:
with _idle_turntable_lock:
_idle_turntable_busy = False
_write_turntable_static()
break # one view per cycle; re-check idle on next loop
@@ -427,6 +431,7 @@ def on_startup():
threading.Thread(target=_watch_frontend, daemon=True).start()
threading.Thread(target=_idle_turntable_daemon, daemon=True).start()
threading.Thread(target=_consistency_check_daemon, daemon=True).start()
threading.Thread(target=_privacy_monitor_daemon, daemon=True).start()
# Mount wireframe static dir for browser video preview
try:
wf_dir = _load_wireframe_dir()
@@ -499,7 +504,10 @@ def _comfy_queue(graph: dict, client_id: str) -> str:
def _comfy_wait(prompt_id: str, deadline: float) -> dict:
"""Poll /history until the prompt finishes; return its outputs dict."""
global _last_user_generation_time
while time.time() < deadline:
if not _idle_turntable_busy:
_last_user_generation_time = time.time()
r = requests.get(f"{COMFY}/history/{prompt_id}", timeout=30)
if r.status_code == 200:
hist = r.json()
@@ -1135,6 +1143,9 @@ def _run_pipeline(
scheduler: str = "beta",
extra_images: list = None, # additional PIL images wired to image2, image3
) -> bytes:
global _last_user_generation_time
if not _idle_turntable_busy:
_last_user_generation_time = time.time()
area = max_area if max_area > 0 else MAX_AREA
pil, w, h = _prep_image(pil, area)
buf = io.BytesIO()
@@ -1226,6 +1237,8 @@ def _run_pipeline(
client_id = uuid.uuid4().hex
prompt_id = _comfy_queue(graph, client_id)
outputs = _comfy_wait(prompt_id, time.time() + GEN_TIMEOUT)
if not _idle_turntable_busy:
_last_user_generation_time = time.time()
png_bytes = _comfy_fetch_image(outputs)
if post_process == "sam2":
@@ -1276,6 +1289,29 @@ def _move_to_trash(filepath: str):
_static_write_lock = threading.Lock()
_invalidate_timer: "threading.Timer | None" = None
_invalidate_timer_lock = threading.Lock()
_privacy_locked = False
_privacy_lock = threading.Lock()
def _privacy_monitor_daemon():
"""Monitors GNOME screen lock via D-Bus gdbus monitor."""
global _privacy_locked
print("[privacy] Starting monitor daemon...")
# Monitor ScreenSaver state changes
cmd = ["gdbus", "monitor", "--session", "--dest", "org.gnome.ScreenSaver", "--object-path", "/org/gnome/ScreenSaver"]
try:
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
for line in proc.stdout:
# Lines look like: /org/gnome/ScreenSaver: org.gnome.ScreenSaver.ActiveChanged (true,)
if "ActiveChanged" in line:
is_locked = "(true,)" in line
with _privacy_lock:
if _privacy_locked != is_locked:
_privacy_locked = is_locked
print(f"[privacy] System lock state change detected: {is_locked}")
_write_all_static()
except Exception as e:
print(f"[privacy] Monitor error (is gdbus installed?): {e}")
def _write_json(path: str, data) -> None:
@@ -1287,7 +1323,7 @@ def _write_json(path: str, data) -> None:
def _write_all_static() -> None:
"""Regenerate <output_dir>/_data/{images,names,groups,group-names,videos}.json.
"""Regenerate <output_dir>/_data/{images,names,groups,group-names,videos,poses}.json.
Called on startup and after every mutation so the browser can\
fetch pre-computed JSON instead of hitting the DB on each page load.
@@ -1371,6 +1407,12 @@ def _write_all_static() -> None:
_write_json(os.path.join(data_dir, "videos.json"),
_get_grouped_wireframes(wireframe_dir))
# poses.json — pose library from poses.md
_write_json(os.path.join(data_dir, "poses.json"), _load_poses())
# system_status.json — for privacy lock etc
_write_json(os.path.join(data_dir, "system_status.json"), {"locked": _privacy_locked})
# config.json — current config snapshot
try:
with open(CONFIG_PATH, "r") as _f:
@@ -1505,6 +1547,18 @@ def _write_turntable_static() -> None:
# Sort: complete first, then by most views done
turntables.sort(key=lambda t: (-int(t["completed"]), -t["n_done"]))
_write_json(os.path.join(data_dir, "turntables.json"), {"turntables": turntables})
summary = tc.get_status_summary(output_dir)
n_complete = sum(1 for v in summary.values() if v["completed"])
status_payload = {
"groups": summary,
"n_complete": n_complete,
"n_total": len(summary),
"is_generating": _idle_turntable_busy,
"is_paused": _idle_turntable_paused,
"idle_seconds": round(time.time() - _last_user_generation_time, 1),
}
_write_json(os.path.join(data_dir, "turntable_status.json"), status_payload)
except Exception as e:
print(f"[static] write_turntable error: {e}")
@@ -1947,6 +2001,17 @@ def get_batch(job_id: str):
@app.get("/images")
def list_images(archived: bool = False):
output_dir = _load_output_dir()
static_file = os.path.join(output_dir, "_data", "images.json")
if os.path.exists(static_file):
try:
with open(static_file, "r") as f:
data = json.load(f)
imgs = data.get("images", [])
if not archived:
imgs = [x for x in imgs if not x.get("archived")]
return {"images": imgs}
except Exception as static_err:
print(f"[static-get] Failed to load images.json: {static_err}")
all_extensions = ('.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.svg') + VIDEO_EXTENSIONS
try:
try:
@@ -2070,6 +2135,14 @@ def _get_grouped_wireframes(wireframe_dir: str) -> dict:
@app.get("/videos")
def list_videos():
"""Return available wireframe template videos and grouped wireframe assets."""
output_dir = _load_output_dir()
static_file = os.path.join(output_dir, "_data", "videos.json")
if os.path.exists(static_file):
try:
with open(static_file, "r") as f:
return json.load(f)
except Exception as static_err:
print(f"[static-get] Failed to load videos.json: {static_err}")
wireframe_dir = _load_wireframe_dir()
return _get_grouped_wireframes(wireframe_dir)
@@ -2410,6 +2483,14 @@ def tag_image(req: TagRequest):
@app.get("/names")
def get_names():
output_dir = _load_output_dir()
static_file = os.path.join(output_dir, "_data", "names.json")
if os.path.exists(static_file):
try:
with open(static_file, "r") as f:
return json.load(f)
except Exception as static_err:
print(f"[static-get] Failed to load names.json: {static_err}")
try:
persons = database.list_persons()
return {p[0]: p[1] for p in persons if p[1]}
@@ -2433,6 +2514,14 @@ def set_name(filename: str, body: dict):
@app.get("/groups")
def get_groups():
output_dir = _load_output_dir()
static_file = os.path.join(output_dir, "_data", "groups.json")
if os.path.exists(static_file):
try:
with open(static_file, "r") as f:
return json.load(f)
except Exception as static_err:
print(f"[static-get] Failed to load groups.json: {static_err}")
try:
persons = database.list_persons()
return {p[0]: p[2] for p in persons if p[2]}
@@ -2478,6 +2567,14 @@ def extract_from_group(req: ExtractRequest):
@app.get("/group-names")
def get_group_names():
output_dir = _load_output_dir()
static_file = os.path.join(output_dir, "_data", "group-names.json")
if os.path.exists(static_file):
try:
with open(static_file, "r") as f:
return json.load(f)
except Exception as static_err:
print(f"[static-get] Failed to load group-names.json: {static_err}")
try:
return database.get_all_group_names()
except Exception as e:
@@ -2632,6 +2729,29 @@ def health():
raise HTTPException(503, f"ComfyUI unreachable at {COMFY}: {e}")
@app.get("/privacy/status")
def get_privacy_status():
return {"locked": _privacy_locked}
@app.post("/privacy/lock")
def set_privacy_lock():
global _privacy_locked
with _privacy_lock:
_privacy_locked = True
_write_all_static()
return {"status": "locked"}
@app.post("/privacy/unlock")
def set_privacy_unlock():
global _privacy_locked
with _privacy_lock:
_privacy_locked = False
_write_all_static()
return {"status": "unlocked"}
def _crop_to_bbox(pil_img: Image.Image, margin: int = 20, top_margin: int = 20, headroom: float = 0.05) -> Image.Image:
if pil_img.mode != 'RGBA':
return pil_img
@@ -5356,7 +5476,7 @@ def get_turntable_status():
"n_total": len(summary),
"is_generating": _idle_turntable_busy,
"is_paused": _idle_turntable_paused,
"idle_seconds": round(time.time() - _last_request_time, 1),
"idle_seconds": round(time.time() - _last_user_generation_time, 1),
}
@@ -5383,6 +5503,7 @@ def pause_turntable():
"""Pause idle background generation."""
global _idle_turntable_paused
_idle_turntable_paused = True
_write_turntable_static()
return {"paused": True}
@@ -5391,6 +5512,7 @@ def resume_turntable():
"""Resume idle background generation."""
global _idle_turntable_paused
_idle_turntable_paused = False
_write_turntable_static()
return {"paused": False}

View File

@@ -32,6 +32,11 @@ should be on the exposed genitals, capturing their natural form and contours wit
naturally replace the single female from Image 1 with the teenage female from image 2.
Keep characteristics of person in Image 2
# Scene Steel replace (18)
replace the female in Image 1 by the teenage female in Image 2. Keep the Identical person in image 2. photo realistic
# Undress
masterpiece, high quality,