diff --git a/backlog.md b/backlog.md index db148d2..accee85 100644 --- a/backlog.md +++ b/backlog.md @@ -69,7 +69,10 @@ Scenery: - samengestelde outpaint featurue lijkt niet goed te werken, but individual steps work perfect. - regenerate a single orbit - pose - when generating a known pose, we should know roughly the image ratio. Make a pose-table, when a pose is generated store the wireframe and meta data -- we can rate an image if its OVER21, manually now in the UI. this is fine, however we want to backfill this TAG for evey image +- we can rate an image if its OVER21, manually now in the UI. this is fine, however we want to backfill this TAG for evey image +- backfill, people-count, 21+, IS_COMPLETE, RESTRAINT +- the wireframe / videos / sub-clips worden niet direct weergegeven +- ## refine - when refresh page, we lose track of current jobs running. diff --git a/ph_downloader.py b/ph_downloader.py new file mode 100755 index 0000000..d7298da --- /dev/null +++ b/ph_downloader.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 +""" +Pornhub Video Downloader Side-Script +Uses yt-dlp under the hood for highly robust and fast downloads. +""" + +import sys +import os +import subprocess + +# Ensure yt-dlp and curl-cffi are installed +try: + import yt_dlp + import curl_cffi +except ImportError: + print("[-] yt-dlp or curl-cffi is not fully installed in this Python environment.") + print("[*] Attempting to install yt-dlp with curl-cffi automatically...") + try: + subprocess.check_call([sys.executable, "-m", "pip", "install", "--upgrade", "yt-dlp[default,curl-cffi]"]) + import yt_dlp + import curl_cffi + print("[+] Successfully installed yt-dlp and curl-cffi!") + except Exception as e: + print(f"[!] Failed to install dependencies automatically: {e}") + print("[!] Please install manually with: pip install \"yt-dlp[default,curl-cffi]\"") + sys.exit(1) + +import argparse + +def download_video(url, output_dir=".", quality="best"): + print(f"[*] Initializing download for: {url}") + print(f"[*] Output directory: {os.path.abspath(output_dir)}") + + if not os.path.exists(output_dir): + os.makedirs(output_dir, exist_ok=True) + + # Format selector configuration + # 'best' is the safest format because it downloads pre-merged streams and doesn't strictly require ffmpeg. + # 'bestvideo+bestaudio/best' will download separate video/audio streams and merge them if ffmpeg is present. + format_selector = 'best' + if quality != 'best': + if quality.endswith('p'): + height = quality[:-1] + format_selector = f'bestvideo[height<={height}]+bestaudio/best[height<={height}]' + else: + format_selector = quality + + ydl_opts = { + 'format': format_selector, + 'outtmpl': os.path.join(output_dir, '%(title)s [%(id)s].%(ext)s'), + 'noplaylist': True, + # Pornhub has age gates; standard user-agent and headers are handled by yt-dlp automatically, + # but let's add some basic robust options. + 'ignoreerrors': False, + 'logtostderr': False, + 'quiet': False, + 'no_warnings': False, + 'nocheckcertificate': True, + } + + # Setup browser impersonation to bypass Cloudflare/TLS fingerprinting blocks (e.g. HTTP 403 Forbidden) + try: + from yt_dlp.networking.impersonate import ImpersonateTarget + ydl_opts['impersonate'] = ImpersonateTarget.from_str('chrome') + except (ImportError, AttributeError): + ydl_opts['impersonate'] = 'chrome' + + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=True) + filename = ydl.prepare_filename(info) + print("\n[+] Download completed successfully!") + print(f"[+] File saved to: {filename}") + return True + except Exception as e: + print(f"\n[!] Error downloading video: {e}") + # Try a safe fallback to 'best' format if we tried a complex format query + if format_selector != 'best': + print("[*] Retrying with fallback quality ('best')...") + ydl_opts['format'] = 'best' + try: + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(url, download=True) + filename = ydl.prepare_filename(info) + print("\n[+] Download completed successfully via fallback!") + print(f"[+] File saved to: {filename}") + return True + except Exception as fe: + print(f"[!] Fallback download also failed: {fe}") + return False + +def main(): + parser = argparse.ArgumentParser(description="Pornhub / General Video Downloader") + parser.add_argument("url", nargs="?", help="URL of the video to download") + parser.add_argument("-o", "--output", default="/mnt/zim/tour-comfy/wireframe/", help="Output directory path (default: current directory)") + parser.add_argument("-q", "--quality", default="720p", help="Video quality (e.g. best, 1080p, 720p, 480p) (default: 720p)") + + args = parser.parse_args() + + url = args.url + if not url: + # Interactive mode + try: + url = input("Enter video URL (e.g., Pornhub link): ").strip() + except (KeyboardInterrupt, EOFError): + print("\n[-] Cancelled.") + sys.exit(0) + + if not url: + print("[!] No URL provided. Exiting.") + sys.exit(1) + + success = download_video(url, args.output, args.quality) + sys.exit(0 if success else 1) + +if __name__ == "__main__": + main() diff --git a/tour-comfy/car.html b/tour-comfy/car.html index b187c1c..2f793e4 100644 --- a/tour-comfy/car.html +++ b/tour-comfy/car.html @@ -2048,6 +2048,8 @@ + + @@ -2077,6 +2079,7 @@ +
@@ -2114,6 +2117,37 @@
+
+
+ - + - +
+
+ - + TRACKING +
+
+
+
+
@@ -2130,7 +2164,7 @@ - + @@ -2148,6 +2182,7 @@
+
@@ -2299,6 +2334,327 @@ // --- HYDRATION_START --- const PRELOADED_IMAGES = [ + "20260629_120459_fs_bitch insemination_dup_20260626_003125_dup_Pasted image (9).png.mp4", + "20260624_154536_img_72.png", + "20260629_114451_sc_sc_image.nobg.png", + "20260629_114346_sc_sc_image.nobg.png", + "_turntable/cg_4e2b6449/views/view_023_345deg.png", + "_turntable/up_ad4000d7/views/view_023_345deg.png", + "_turntable/up_ad4000d7/views/view_022_330deg.png", + "_turntable/up_ad4000d7/views/view_021_315deg.png", + "_turntable/up_ad4000d7/views/view_020_300deg.png", + "_turntable/up_ad4000d7/views/view_019_285deg.png", + "_turntable/up_ad4000d7/views/view_018_270deg.png", + "_turntable/up_ad4000d7/views/view_017_255deg.png", + "_turntable/up_ad4000d7/views/view_016_240deg.png", + "_turntable/up_ad4000d7/views/view_015_225deg.png", + "_turntable/up_ad4000d7/views/view_014_210deg.png", + "_turntable/up_ad4000d7/views/view_013_195deg.png", + "_turntable/up_ad4000d7/views/view_012_180deg.png", + "_turntable/up_ad4000d7/views/view_011_165deg.png", + "_turntable/up_ad4000d7/views/view_010_150deg.png", + "_turntable/up_ad4000d7/views/view_009_135deg.png", + "_turntable/up_ad4000d7/views/view_008_120deg.png", + "_turntable/up_ad4000d7/views/view_007_105deg.png", + "_turntable/up_ad4000d7/views/view_006_090deg.png", + "_turntable/up_ad4000d7/views/view_005_075deg.png", + "_turntable/up_ad4000d7/views/view_004_060deg.png", + "_turntable/up_ad4000d7/views/view_003_045deg.png", + "_turntable/up_ad4000d7/views/view_002_030deg.png", + "_turntable/up_ad4000d7/views/view_001_015deg.png", + "_turntable/up_ad4000d7/views/view_000_000deg.png", + "_turntable/cg_e6ba3260/views/view_023_345deg.png", + "_turntable/cg_e6ba3260/views/view_022_330deg.png", + "_turntable/cg_e6ba3260/views/view_021_315deg.png", + "_turntable/cg_e6ba3260/views/view_020_300deg.png", + "_turntable/cg_e6ba3260/views/view_019_285deg.png", + "_turntable/cg_e6ba3260/views/view_018_270deg.png", + "_turntable/cg_e6ba3260/views/view_017_255deg.png", + "_turntable/cg_e6ba3260/views/view_016_240deg.png", + "_turntable/cg_e6ba3260/views/view_015_225deg.png", + "_turntable/cg_e6ba3260/views/view_014_210deg.png", + "_turntable/cg_e6ba3260/views/view_013_195deg.png", + "_turntable/cg_e6ba3260/views/view_012_180deg.png", + "_turntable/cg_e6ba3260/views/view_011_165deg.png", + "_turntable/cg_e6ba3260/views/view_010_150deg.png", + "_turntable/cg_e6ba3260/views/view_009_135deg.png", + "_turntable/cg_e6ba3260/views/view_008_120deg.png", + "_turntable/cg_e6ba3260/views/view_007_105deg.png", + "_turntable/cg_e6ba3260/views/view_006_090deg.png", + "_turntable/cg_e6ba3260/views/view_005_075deg.png", + "_turntable/cg_e6ba3260/views/view_004_060deg.png", + "_turntable/cg_e6ba3260/views/view_003_045deg.png", + "_turntable/cg_e6ba3260/views/view_002_030deg.png", + "_turntable/cg_e6ba3260/views/view_001_015deg.png", + "_turntable/cg_e6ba3260/views/view_000_000deg.png", + "_turntable/cg_a9282b3b/views/view_009_135deg.png", + "_turntable/cg_a9282b3b/views/view_008_120deg.png", + "_turntable/cg_a9282b3b/views/view_007_105deg.png", + "_turntable/cg_a9282b3b/views/view_006_090deg.png", + "_turntable/cg_a9282b3b/views/view_005_075deg.png", + "_turntable/cg_a9282b3b/views/view_004_060deg.png", + "_turntable/cg_a9282b3b/views/view_003_045deg.png", + "_turntable/cg_a9282b3b/views/view_002_030deg.png", + "_turntable/cg_a9282b3b/views/view_001_015deg.png", + "_turntable/cg_a9282b3b/views/view_000_000deg.png", + "20260629_063504_12_20260629_063237_image.png", + "20260629_063452_11_20260629_063237_image.png", + "20260629_063441_10_20260629_063237_image.png", + "20260629_063430_9_20260629_063237_image.png", + "20260629_063418_8_20260629_063237_image.png", + "20260629_063407_7_20260629_063237_image.png", + "20260629_063356_6_20260629_063237_image.png", + "20260629_063345_5_20260629_063237_image.png", + "20260629_063333_4_20260629_063237_image.png", + "20260629_063322_3_20260629_063237_image.png", + "20260629_063311_2_20260629_063237_image.png", + "20260629_063300_1_20260629_063237_image.png", + "20260629_063248_0_20260629_063237_image.png", + "20260629_063237_image.png", + "20260629_063200_12_20260629_062932_image.png", + "20260629_063149_11_20260629_062932_image.png", + "20260629_063138_10_20260629_062932_image.png", + "20260629_063127_9_20260629_062932_image.png", + "20260629_063116_8_20260629_062932_image.png", + "20260629_063105_7_20260629_062932_image.png", + "20260629_063054_6_20260629_062932_image.png", + "20260629_063043_5_20260629_062932_image.png", + "20260629_063032_4_20260629_062932_image.png", + "20260629_063022_3_20260629_062932_image.png", + "20260629_063011_2_20260629_062932_image.png", + "20260629_063000_1_20260629_062932_image.png", + "20260629_062949_0_20260629_062932_image.png", + "20260629_062932_image.png", + "20260629_060926_12_20260629_060652_image.png", + "20260629_060915_11_20260629_060652_image.png", + "20260629_060904_10_20260629_060652_image.png", + "20260629_060853_9_20260629_060652_image.png", + "20260629_060842_8_20260629_060652_image.png", + "20260629_060831_7_20260629_060652_image.png", + "20260629_060820_6_20260629_060652_image.png", + "20260629_060809_5_20260629_060652_image.png", + "20260629_060747_3_20260629_060652_image.png", + "20260629_060736_2_20260629_060652_image.png", + "20260629_060725_1_20260629_060652_image.png", + "20260629_060714_0_20260629_060652_image.png", + "20260629_060652_image.png", + "cg_fb1043d8_face.png", + "20260629_055242_image.png", + "_turntable/cg_2bbb563a/views/view_022_330deg.png", + "_turntable/cg_2bbb563a/views/view_003_045deg.png", + "_turntable/cg_2bbb563a/views/20260629_054801_dup_view_003_045deg.png", + "_turntable/cg_2bbb563a/views/view_023_345deg.png", + "_turntable/cg_2bbb563a/views/view_021_315deg.png", + "_turntable/cg_2bbb563a/views/view_020_300deg.png", + "_turntable/cg_2bbb563a/views/view_019_285deg.png", + "_turntable/cg_2bbb563a/views/view_018_270deg.png", + "_turntable/cg_2bbb563a/views/view_017_255deg.png", + "_turntable/cg_2bbb563a/views/view_016_240deg.png", + "_turntable/cg_2bbb563a/views/view_015_225deg.png", + "_turntable/cg_2bbb563a/views/view_014_210deg.png", + "_turntable/cg_2bbb563a/views/view_013_195deg.png", + "_turntable/cg_2bbb563a/views/view_012_180deg.png", + "_turntable/cg_2bbb563a/views/view_011_165deg.png", + "_turntable/cg_2bbb563a/views/view_010_150deg.png", + "_turntable/cg_2bbb563a/views/view_009_135deg.png", + "_turntable/cg_2bbb563a/views/view_008_120deg.png", + "_turntable/cg_2bbb563a/views/view_007_105deg.png", + "_turntable/cg_2bbb563a/views/view_006_090deg.png", + "_turntable/cg_2bbb563a/views/view_005_075deg.png", + "_turntable/cg_2bbb563a/views/view_004_060deg.png", + "_turntable/cg_2bbb563a/views/view_002_030deg.png", + "_turntable/cg_2bbb563a/views/view_001_015deg.png", + "_turntable/cg_2bbb563a/views/view_000_000deg.png", + "cg_2bbb563a_face.png", + "20260629_044126_sc_image.png", + "20260629_043959_sc_image.png", + "20260628_154609_image.nobg.png", + "20260629_043853_sc_image.png", + "20260629_042417_sc_image.png", + "20260629_041152_sc_image.nobg.png", + "20260629_041152_sc_image.png", + "20260629_040857_sc_image.png", + "20260629_040648_sc_image.png", + "20260629_040418_dup_20260621_032709_image.nobg.nobg.png", + "20260629_040418_dup_20260621_032709_image.nobg.png", + "20260629_040025_sc_image.nobg.png", + "_turntable/image.png/views/20260629_035933_sc_view_000_000deg.png", + "20260629_034754_sc_0_20260622_101128_image.png", + "20260629_034712_sc_0_20260622_101128_image.png", + "20260629_034537_sc_0_20260622_101128_image.png", + "20260629_034527_sc_0_20260622_101128_image.png", + "20260629_034144_sc_0_20260622_101128_image.png", + "20260629_034023_sc_0_20260622_101128_image.png", + "20260629_033851_sc_0_20260622_101128_image.png", + "20260629_033631_sc_2_20260622_101128_image.nobg.png", + "20260629_032345_mr_sc_dup_20260624_055327_image.nobg.png", + "20260629_032136_mr_sc_dup_20260624_055327_image.nobg.png", + "20260629_031800_mr_sc_dup_20260624_055327_image.nobg.png", + "20260629_031259_image.png", + "20260629_030904_sc_dup_20260624_055327_image.nobg.png", + "20260629_030742_sc_dup_20260624_055327_image.nobg.png", + "20260629_030705_sc_dup_20260624_055327_image.nobg.png", + "20260629_030537_sc_image.nobg.nobg.png", + "20260629_025630_sc_image.nobg.nobg.png", + "20260629_024315_image.nobg.nobg.png", + "20260629_025420_sc_image.nobg.png", + "20260629_024911_sc_image.nobg.png", + "20260629_024824_sc_image.nobg.png", + "20260629_024707_image.nobg.png", + "20260629_024707_image.png", + "20260629_024511_sc_image.nobg.png", + "20260629_024413_sc_image.nobg.png", + "20260629_024305_sc_dup_20260624_055327_image.nobg.nobg.nobg.png", + "20260629_024315_image.nobg.png", + "20260629_024305_sc_dup_20260624_055327_image.nobg.nobg.png", + "20260629_024246_mr_sc_dup_20260624_055327_image.nobg.nobg.png", + "20260629_024037_sc_dup_20260624_055327_image.nobg.nobg.png", + "20260629_021748_dup_20260624_055327_image.nobg.nobg.png", + "20260624_055049_image.nobg.png", + "20260629_015442_image.nobg.png", + "20260629_015127_image.nobg.png", + "20260629_012800_mr_sc_dup_20260624_055327_image.nobg.png", + "20260629_012559_mr_sc_dup_20260624_055327_image.nobg.png", + "20260629_012137_sc_image.nobg.png", + "20260629_012025_sc_sc_dup_20260624_055327_image.nobg.png", + "20260629_011925_sc_sc_dup_20260624_055327_image.nobg.png", + "20260629_011741_sc_dup_20260624_055327_image.nobg.png", + "20260628_192939_dup_20260624_055327_image.nobg.nobg.png", + "20260629_004439_sc_dup_20260624_055327_image.nobg.png", + "20260629_004407_sc_dup_20260624_055327_image.nobg.png", + "20260629_004301_sc_dup_20260624_055327_image.nobg.png", + "20260629_004236_sc_dup_20260624_055327_image.nobg.png", + "20260629_003942_sc_dup_20260624_055327_image.nobg.png", + "20260629_003839_sc_image.nobg.png", + "20260629_003636_sc_image.nobg.png", + "20260629_003507_sc_image.nobg.png", + "20260629_003223_sc_image.png", + "20260629_003132_sc_image.png", + "20260629_003014_sc_image.nobg.png", + "20260629_002909_sc_image.nobg.png", + "20260629_002329_3_20260628_205049_image.png", + "20260629_002318_3_20260628_205049_image.png", + "20260629_002307_3_20260628_205049_image.png", + "20260629_002128_image.png", + "20260629_002117_image.png", + "20260629_002030_3_20260628_205049_image.png", + "20260629_002019_3_20260628_205049_image.png", + "20260629_001558_image.nobg.png", + "20260629_001547_image.nobg.png", + "20260629_001536_image.nobg.png", + "20260629_001525_image.nobg.png", + "20260629_001514_image.nobg.png", + "20260629_001502_image.nobg.png", + "20260629_001451_image.nobg.png", + "20260629_001429_image.nobg.png", + "20260629_001418_image.nobg.png", + "20260629_001408_image.nobg.png", + "20260629_001357_image.nobg.png", + "20260629_001225_image.nobg.png", + "image.png_face.png", + "20260629_000834_image.nobg.png", + "20260629_000906_image.png", + "20260629_000855_image.png", + "20260629_000844_image.png", + "20260629_000834_image.png", + "20260629_000532_image.png", + "20260629_000232_image.png", + "20260629_000101_image.png", + "20260629_000022_image.png", + "20260628_235923_3_20260628_205049_image.png", + "20260628_235601_3_20260628_205049_image.png", + "20260628_235157_image.png", + "20260628_234315_image.png", + "_turntable/image.png/views/view_000_000deg.png", + "20260628_222828_0_20260628_222816_image.png", + "_turntable/image.png/views/view_023_345deg.png", + "_turntable/image.png/views/view_022_330deg.png", + "_turntable/image.png/views/view_021_315deg.png", + "_turntable/image.png/views/view_020_300deg.png", + "_turntable/image.png/views/view_019_285deg.png", + "_turntable/image.png/views/view_018_270deg.png", + "_turntable/image.png/views/view_017_255deg.png", + "_turntable/image.png/views/view_016_240deg.png", + "_turntable/image.png/views/view_015_225deg.png", + "_turntable/image.png/views/view_014_210deg.png", + "_turntable/image.png/views/view_013_195deg.png", + "_turntable/image.png/views/view_012_180deg.png", + "_turntable/image.png/views/view_011_165deg.png", + "_turntable/image.png/views/view_010_150deg.png", + "_turntable/image.png/views/view_009_135deg.png", + "_turntable/image.png/views/view_008_120deg.png", + "_turntable/image.png/views/view_007_105deg.png", + "_turntable/image.png/views/view_006_090deg.png", + "_turntable/image.png/views/view_005_075deg.png", + "_turntable/image.png/views/view_004_060deg.png", + "_turntable/image.png/views/view_003_045deg.png", + "_turntable/image.png/views/view_002_030deg.png", + "_turntable/image.png/views/view_001_015deg.png", + "20260628_205437_12_20260628_205049_image.png", + "20260628_205421_11_20260628_205049_image.png", + "20260628_205404_10_20260628_205049_image.png", + "20260628_205348_9_20260628_205049_image.png", + "20260628_205336_8_20260628_205049_image.png", + "20260628_205325_7_20260628_205049_image.png", + "20260628_205302_5_20260628_205049_image.png", + "20260628_205240_3_20260628_205049_image.png", + "20260628_205229_2_20260628_205049_image.png", + "20260628_205217_1_20260628_205049_image.png", + "20260628_205206_0_20260628_205049_image.png", + "20260628_205049_image.png", + "20260628_204427_image.png", + "20260628_192939_dup_20260624_055327_image.nobg.png", + "cg_18040099_face.png", + "20260628_191913_dup_20260624_055327_image.nobg.png", + "20260628_191837_dup_20260624_055327_image.nobg.png", + "20260628_191628_dup_20260624_055327_image.nobg.png", + "20260628_191504_dup_20260624_055327_image.nobg.png", + "20260628_191427_dup_20260624_055327_image.nobg.png", + "20260628_191411_dup_20260624_055327_image.nobg.png", + "20260628_191311_dup_20260624_055327_image.nobg.png", + "20260628_191244_dup_20260624_055327_image.nobg.png", + "20260628_191156_dup_20260624_055327_image.nobg.png", + "20260628_191124_dup_20260624_055327_image.nobg.png", + "20260628_190719_dup_20260624_055327_image.nobg.png", + "20260628_190648_dup_20260624_125526_dup_20260624_055327_image.nobg.png", + "20260628_190535_dup_20260624_125526_dup_20260624_055327_image.nobg.png", + "20260628_190448_dup_20260624_125526_dup_20260624_055327_image.nobg.png", + "_turntable/up_f475c600/views/20260628_190319_view_023_345deg.png", + "20260628_184957_image.png", + "20260628_184858_dup_20260624_055327_image.nobg.png", + "20260628_175708_dup_20260624_125526_dup_20260624_055327_image.nobg.png", + "20260628_175639_dup_20260624_125526_dup_20260624_055327_image.nobg.png", + "20260628_175556_dup_20260624_125526_dup_20260624_055327_image.nobg.png", + "20260628_175521_dup_20260624_125526_dup_20260624_055327_image.nobg.png", + "cg_f0049873_face.png", + "20260628_173904_dup_20260624_055327_image.nobg.png", + "_turntable/up_f475c600/views/20260628_173436_view_023_345deg.png", + "_turntable/up_f475c600/views/20260628_173114_view_023_345deg.png", + "20260628_173104_up_f475c600_face.png", + "_turntable/up_f475c600/views/view_023_345deg.png", + "20260628_172359_image.png", + "cg_d032c1ef_face.png", + "_turntable/cg_d032c1ef/views/view_023_345deg.png", + "_turntable/cg_d032c1ef/views/view_000_000deg.png", + "_turntable/cg_d032c1ef/views/view_009_135deg.png", + "_turntable/cg_d032c1ef/views/view_001_015deg.png", + "_turntable/cg_d032c1ef/views/view_002_030deg.png", + "20260618_054230_image.png_face.png", + "cg_f1e85987_face.png", + "up_f475c600_face.png", + "20260618_174516_image.png_face.png", + "up_76ef7e4f_face.png", + "cg_e79533a4_face.png", + "_turntable/cg_e79533a4/views/view_023_345deg.png", + "_turntable/cg_e79533a4/views/view_000_000deg.png", + "_turntable/cg_e79533a4/views/view_012_180deg.png", + "_turntable/cg_e79533a4/views/view_011_165deg.png", + "cg_61b863a8_face.png", + "up_a4d16da7_face.png", + "up_455c085f_face.png", + "up_b64786e7_face.png", + "20260618_133729_image.png_face.png", + "img_77.png_face.png", "20260628_165000_sc_image.png", "20260628_164850_sc_sc_image.png", "20260628_154819_sc_image.png", @@ -2339,8 +2695,8 @@ "20260626_231655_sc_image.nobg.png", "20260618_181600_image.png_face.png", "_turntable/20260618_181600_image.png/views/view_000_000deg.png", - "_turntable/cg_74544975/views/20260628_140758_view_000_000deg.nobg.png", "_turntable/cg_74544975/views/20260628_142102_dup_20260628_140758_view_000_000deg.nobg.png", + "_turntable/cg_74544975/views/20260628_140758_view_000_000deg.nobg.png", "_turntable/cg_74544975/views/20260628_140758_view_000_000deg.png", "up_3a05a2de_face.png", "up_aa1add4d_face.png", @@ -2974,7 +3330,6 @@ "_turntable/up_78ae5271/views/view_002_030deg.png", "_turntable/up_78ae5271/views/view_001_015deg.png", "_turntable/up_78ae5271/views/view_000_000deg.png", - "_turntable/cg_4e2b6449/views/view_023_345deg.png", "_turntable/cg_4e2b6449/views/view_022_330deg.png", "_turntable/cg_4e2b6449/views/view_021_315deg.png", "_turntable/cg_4e2b6449/views/view_020_300deg.png", @@ -3277,7 +3632,6 @@ "_turntable/up_98c8443e/views/view_023_345deg.png", "_turntable/up_98c8443e/views/view_000_000deg.png", "up_7382d00e_face.png", - "up_455c085f_face.png", "_turntable/20150913_211324.jpg/views/view_012_180deg.png", "_turntable/20150913_211324.jpg/views/view_013_195deg.png", "_turntable/20150913_211324.jpg/views/view_014_210deg.png", @@ -3968,7 +4322,6 @@ "_turntable/20260618_134348_image.png/views/view_022_330deg.png", "_turntable/20260618_134348_image.png/views/view_021_315deg.png", "_turntable/20260618_134348_image.png/views/view_020_300deg.png", - "_turntable/20260618_134348_image.png/views/view_019_285deg.png", "_turntable/20260618_134348_image.png/views/view_018_270deg.png", "_turntable/20260618_134348_image.png/views/view_017_255deg.png", "_turntable/20260618_134348_image.png/views/view_016_240deg.png", @@ -4253,7 +4606,6 @@ "20260627_042228_image.nobg.png", "20260627_042228_image.png", "20260627_042256_image.png", - "20260627_042100_pose_image.png", "20260627_042003_image.png", "20260627_041915_image.png", "20260627_041701_image.png", @@ -4287,7 +4639,6 @@ "20260627_031104_sc_dup_20260624_125526_dup_20260624_055327_image.nobg.png", "20260627_030811_sc_dup_20260624_125526_dup_20260624_055327_image.nobg.png", "20260627_030739_sc_dup_20260624_125526_dup_20260624_055327_image.nobg.png", - "cg_18040099_face.png", "20260627_030425_dup_20260624_125526_dup_20260624_055327_image.nobg.png", "20260627_030425_dup_20260624_125526_dup_20260624_055327_image.nobg.nobg.png", "20260627_030344_sc_image.png", @@ -4389,7 +4740,6 @@ "_turntable/out4.png/views/view_002_030deg.png", "_turntable/out4.png/views/view_001_015deg.png", "_turntable/out4.png/views/view_000_000deg.png", - "_turntable/cg_d032c1ef/views/view_023_345deg.png", "_turntable/cg_d032c1ef/views/view_022_330deg.png", "_turntable/cg_d032c1ef/views/view_021_315deg.png", "_turntable/cg_d032c1ef/views/view_020_300deg.png", @@ -4403,16 +4753,12 @@ "_turntable/cg_d032c1ef/views/view_012_180deg.png", "_turntable/cg_d032c1ef/views/view_011_165deg.png", "_turntable/cg_d032c1ef/views/view_010_150deg.png", - "_turntable/cg_d032c1ef/views/view_009_135deg.png", "_turntable/cg_d032c1ef/views/view_008_120deg.png", "_turntable/cg_d032c1ef/views/view_007_105deg.png", "_turntable/cg_d032c1ef/views/view_006_090deg.png", "_turntable/cg_d032c1ef/views/view_005_075deg.png", "_turntable/cg_d032c1ef/views/view_004_060deg.png", "_turntable/cg_d032c1ef/views/view_003_045deg.png", - "_turntable/cg_d032c1ef/views/view_002_030deg.png", - "_turntable/cg_d032c1ef/views/view_001_015deg.png", - "_turntable/cg_d032c1ef/views/view_000_000deg.png", "_turntable/20260618_174516_image.png/views/view_023_345deg.png", "_turntable/20260618_174516_image.png/views/view_022_330deg.png", "_turntable/20260618_174516_image.png/views/view_021_315deg.png", @@ -4509,7 +4855,6 @@ "_turntable/up_76ef7e4f/views/view_002_030deg.png", "_turntable/up_76ef7e4f/views/view_001_015deg.png", "_turntable/up_76ef7e4f/views/view_000_000deg.png", - "_turntable/up_a4d16da7/turntable.mp4", "_turntable/up_a4d16da7/views/view_023_345deg.png", "_turntable/up_a4d16da7/views/view_022_330deg.png", "_turntable/up_a4d16da7/views/view_021_315deg.png", @@ -4607,7 +4952,6 @@ "_turntable/cg_7c7b22c9/views/view_002_030deg.png", "_turntable/cg_7c7b22c9/views/view_001_015deg.png", "_turntable/cg_7c7b22c9/views/view_000_000deg.png", - "cg_e79533a4_face.png", "cg_73e141fa_face.png", "20260626_154945_image.png", "20260626_154934_image.png", @@ -4865,7 +5209,6 @@ "_turntable/up_ab858b0a/views/view_002_030deg.png", "_turntable/up_ab858b0a/views/view_001_015deg.png", "_turntable/up_ab858b0a/views/view_000_000deg.png", - "_turntable/up_f475c600/views/view_023_345deg.png", "_turntable/up_f475c600/views/view_022_330deg.png", "_turntable/up_f475c600/views/view_021_315deg.png", "_turntable/up_f475c600/views/view_020_300deg.png", @@ -4933,7 +5276,6 @@ "_turntable/img_77.png/views/view_002_030deg.png", "_turntable/img_77.png/views/view_001_015deg.png", "_turntable/img_77.png/views/view_000_000deg.png", - "_turntable/cg_e79533a4/views/view_023_345deg.png", "_turntable/cg_e79533a4/views/view_022_330deg.png", "_turntable/cg_e79533a4/views/view_021_315deg.png", "_turntable/cg_e79533a4/views/view_020_300deg.png", @@ -4944,8 +5286,6 @@ "_turntable/cg_e79533a4/views/view_015_225deg.png", "_turntable/cg_e79533a4/views/view_014_210deg.png", "_turntable/cg_e79533a4/views/view_013_195deg.png", - "_turntable/cg_e79533a4/views/view_012_180deg.png", - "_turntable/cg_e79533a4/views/view_011_165deg.png", "_turntable/cg_e79533a4/views/view_010_150deg.png", "_turntable/cg_e79533a4/views/view_009_135deg.png", "_turntable/cg_e79533a4/views/view_008_120deg.png", @@ -4956,7 +5296,6 @@ "_turntable/cg_e79533a4/views/view_003_045deg.png", "_turntable/cg_e79533a4/views/view_002_030deg.png", "_turntable/cg_e79533a4/views/view_001_015deg.png", - "_turntable/cg_e79533a4/views/view_000_000deg.png", "_turntable/b1.png/views/view_023_345deg.png", "_turntable/b1.png/views/view_022_330deg.png", "_turntable/b1.png/views/view_021_315deg.png", @@ -5016,8 +5355,8 @@ "20260626_025708_2_20260625_205722_image.png", "20260626_005644_dup_20260626_005504_Pasted image (9).nobg.png", "20260626_005504_Pasted image (9).nobg.png", - "20260626_005504_Pasted image (9).png", "20260626_005644_dup_20260626_005504_Pasted image (9).png", + "20260626_005504_Pasted image (9).png", "20260626_005322_dup_20260626_003125_dup_Pasted image (9).png", "20260626_003125_dup_Pasted image (9).png", "cg_ad73335e_face.png", @@ -5113,11 +5452,11 @@ "20260625_025335_dup_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", - "20260625_023536_dup_20260624_165109_0_20260622_091731_image.png", "20260624_165109_0_20260622_091731_image.png", + "20260625_023536_dup_20260624_165109_0_20260622_091731_image.png", "up_ab858b0a_face.png", - "20260625_022250_sc_image.png", "20260625_022332_dup_20260625_022250_sc_image.png", + "20260625_022250_sc_image.png", "20260625_022250_sc_image.nobg.png", "20260625_022109_sc_image.png", "20260625_022007_sc_image.png", @@ -5312,9 +5651,9 @@ "20260624_183010_0_20260624_182958_image.png", "20260624_182958_image.png", "20260624_174900_image.png", + "20260624_173115_image.png", "20260624_175711_dup_20260624_174943_dup_20260624_173115_image.png", "20260624_174943_dup_20260624_173115_image.png", - "20260624_173115_image.png", "20260624_174757_image.png", "20260624_174442_image.png", "20260624_172923_image.nobg.png", @@ -5337,8 +5676,8 @@ "20260624_164353_dup_20260624_163951_dup_20260624_163352_dup_20260624_162601_mr_0_20260622_093058_image.png", "20260624_162601_mr_0_20260622_093058_image.nobg.png", "20260624_164059_dup_20260624_163951_dup_20260624_163352_dup_20260624_162601_mr_0_20260622_093058_image.png", - "20260624_163951_dup_20260624_163352_dup_20260624_162601_mr_0_20260622_093058_image.png", "20260624_164116_dup_20260624_163951_dup_20260624_163352_dup_20260624_162601_mr_0_20260622_093058_image.png", + "20260624_163951_dup_20260624_163352_dup_20260624_162601_mr_0_20260622_093058_image.png", "20260624_163634_dup_20260624_163352_dup_20260624_162601_mr_0_20260622_093058_image.png", "20260624_162510_image.png", "20260624_162408_image.png", @@ -5371,7 +5710,6 @@ "20260624_154712_img_72.png", "20260624_154639_img_72.png", "20260624_154548_img_72.png", - "20260624_154536_img_72.png", "20260624_154525_img_72.png", "20260624_154514_img_72.png", "20260624_154503_img_72.png", @@ -5610,7 +5948,6 @@ "20260624_043843_mr_image.png", "20260624_043811_mr_image.png", "20260624_043304_mr_image.png", - "20260624_043253_mr_image.png", "20260624_042912_mr_image.png", "20260624_042437_mr_test123.jpeg", "20260624_042249_image.png", @@ -6137,8 +6474,8 @@ "20260618_010638_b1.png", "20260622_020521_dup_20260622_020223_image.nobg.png", "20260622_021658_dup_20260621_212650_image.nobg.png", - "20260622_020521_dup_20260622_020223_image.png", "20260622_020223_image.png", + "20260622_020521_dup_20260622_020223_image.png", "20260622_013636_image.nobg.png", "20260622_015031_image.png", "20260622_013636_image.png", @@ -6162,8 +6499,8 @@ "20260620_152017_image.png", "20260618_013452_out4.png", "20260620_025959_out4.png", - "20260622_021658_dup_20260621_212650_image.png", "20260621_212650_image.png", + "20260622_021658_dup_20260621_212650_image.png", "20260621_210130_0_20260621_205151_image.png", "20260621_210118_0_20260621_205151_image.png", "20260621_210107_0_20260621_205151_image.png", @@ -6432,7 +6769,6 @@ "20260619_204019_others.jpeg", "20260619_204008_others.jpeg", "20260619_203945_others.jpeg", - "20260619_203934_others.jpeg", "20260619_203923_others.jpeg", "20260619_203912_others.jpeg", "20260619_203901_others.jpeg", @@ -7010,8 +7346,8 @@ "t159zr-1.png", "kbk99v.png", "kk563t.png", - "Pasted image (9).png", "20260626_003137_dup_20260626_003125_dup_Pasted image (9).png", + "Pasted image (9).png", "Pasted image (4).png", "Pasted image (3).png", "Pasted image.png", @@ -7082,6 +7418,10 @@ let fileArchived = {}; // filename → boolean let filePoseDescription = {}; // filename → human readable string let filePoseSkeleton = {}; // filename → JSON string (COCO-17 keypoints) + let filePeopleCount = {}; // filename → integer or null + let fileAnatomicalCompleteness = {}; // filename → boolean or null + let fileFacialDirection = {}; // filename → string or null + let fileObjects = {}; // filename → array of objects let fileTags = {}; // filename → array of strings let fileIsSource = {}; // filename → boolean let _archiveViewActive = false; // when true, show archived items only @@ -7186,6 +7526,62 @@ return group.gid; } + function getGalleryColumnCount() { + const gallery = document.getElementById('gallery'); + if (!gallery) return 1; + const cards = gallery.querySelectorAll('.image-card'); + if (cards.length < 2) return 1; + let count = 0; + const firstTop = cards[0].offsetTop; + for (let i = 0; i < cards.length; i++) { + if (cards[i].offsetTop === firstTop) count++; + else break; + } + return count || 1; + } + + function updateGalleryHighlight() { + const cards = document.querySelectorAll('.image-card'); + let found = null; + cards.forEach(card => { + const gid = card.dataset.group; + const isMatch = gid === _lastSelectedGid; + card.classList.toggle('highlight-prev', isMatch); + if (isMatch) found = card; + }); + if (found && !document.getElementById('studio').classList.contains('open')) { + found.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); + } + } + + function handleGalleryNav(e) { + if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) { + if (!currentGroups || currentGroups.length === 0) return; + let idx = currentGroups.findIndex(g => g.gid === _lastSelectedGid); + if (idx === -1) idx = 0; + const cols = getGalleryColumnCount(); + if (e.key === 'ArrowLeft') idx--; + else if (e.key === 'ArrowRight') idx++; + else if (e.key === 'ArrowUp') idx -= cols; + else if (e.key === 'ArrowDown') idx += cols; + if (idx < 0) idx = 0; + if (idx >= currentGroups.length) idx = currentGroups.length - 1; + const newGid = currentGroups[idx].gid; + if (newGid !== _lastSelectedGid) { + _lastSelectedGid = newGid; + localStorage.setItem('lastSelectedGid', _lastSelectedGid); + updateGalleryHighlight(); + } + e.preventDefault(); + } else if (e.key === 'Enter') { + const card = [...document.querySelectorAll('.image-card')].find(c => c.dataset.group === _lastSelectedGid); + if (card) { + handleCardClick(card); + e.preventDefault(); + } + } + } + function clearCycleTimers() { cycleTimers.forEach(id => clearInterval(id)); cycleTimers.clear(); @@ -7259,6 +7655,10 @@ const isHidden = !!fileHidden[fname]; const isSource = !!fileIsSource[fname]; + if (fileGroups[fname] !== lbCurrentGid) { + return; + } + if (tab === 'hidden' && isHidden && !isArchived) { matched.push(fname); } else if (tab === 'source' && isSource && !isArchived) { @@ -7362,6 +7762,94 @@ } } + async function bulkRelocateGroup() { + if (_selectedFilenames.size === 0) return; + const targetGid = prompt("Enter target Group ID to move the selected images to (or leave blank to generate a new unique group):"); + if (targetGid === null) return; // User canceled + + const list = Array.from(_selectedFilenames); + try { + const r = await fetch(`${API}/groups/merge`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ filenames: list, group_id: targetGid.trim() || null }) + }); + if (r.ok) { + const res = await r.json(); + const finalGid = res.group_id; + showToast(`Moved ${list.length} images to group: ${finalGid}`, 'success'); + + // Update local customGroups and fileGroups mapping immediately + list.forEach(fname => { + customGroups[fname] = finalGid; + fileGroups[fname] = finalGid; + }); + + toggleMultiSelectMode(); + + if (finalGid === lbCurrentGid) { + switchFilmstripTab(_activeFilmstripTab); + } else { + const remaining = lbNames.filter(n => !list.includes(n)); + if (remaining.length === 0) { + closeLightbox(); + } else { + lbIdx = lbNames.indexOf(remaining[0]); + if (lbIdx === -1) lbIdx = 0; + switchFilmstripTab(_activeFilmstripTab); + } + } + await loadImages(); + } else { + const err = await r.text(); + showToast(`Failed to relocate group: ${err}`, 'error'); + } + } catch (e) { + showToast(`Network error: ${e}`, 'error'); + } + } + + async function bulkExtractSelected() { + if (_selectedFilenames.size === 0) return; + if (!confirm(`Are you sure you want to extract ${_selectedFilenames.size} selected image(s) into their own solo groups?`)) return; + + const list = Array.from(_selectedFilenames); + let countSuccess = 0; + try { + await Promise.all(list.map(async (filename) => { + try { + const r = await fetch(`${API}/groups/extract`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ filename }), + }); + if (r.ok) { + customGroups[filename] = `solo:${filename}`; + fileGroups[filename] = filename; + countSuccess++; + } + } catch (e) { + console.error("Error extracting " + filename, e); + } + })); + + showToast(`Successfully extracted ${countSuccess} of ${list.length} images into solo groups`, 'success'); + toggleMultiSelectMode(); + + const remaining = lbNames.filter(n => !list.includes(n)); + if (remaining.length === 0) { + closeLightbox(); + } else { + lbIdx = lbNames.indexOf(remaining[0]); + if (lbIdx === -1) lbIdx = 0; + switchFilmstripTab(_activeFilmstripTab); + } + await loadImages(); + } catch (e) { + showToast(`Error during bulk extraction: ${e}`, 'error'); + } + } + function filmstripThumbDragStart(event, fname) { event.dataTransfer.setData('text/plain', fname); event.dataTransfer.effectAllowed = 'move'; @@ -7652,13 +8140,54 @@ const uniformEl = document.getElementById(isSb ? 'sbPadUniform' : 'padUniform'); if (uniformEl) uniformEl.checked = false; if (isSb) _sbPadUniform = false; + } else if (presetVal.includes(':')) { + // Aspect ratio presets (e.g. "1:1", "4:3") + const img = document.getElementById('lbImg'); + if (!img || !img.naturalWidth) { + showToast('No active image to calculate aspect ratio', 'info'); + return; + } + const parts = presetVal.split(':'); + const targetAspect = parseFloat(parts[0]) / parseFloat(parts[1]); + const w = img.naturalWidth; + const h = img.naturalHeight; + const currentAspect = w / h; + + // Reset all first + sides.forEach(side => { + const el = document.getElementById(prefix + side); + if (el) el.value = '0.00%'; + }); + + if (currentAspect > targetAspect) { + // Too wide -> add height (Top/Bottom) + const targetH = w / targetAspect; + const totalPad = Math.round(targetH - h); + const half = Math.round(totalPad / 2); + const topEl = document.getElementById(prefix + 'Top'); + const bottomEl = document.getElementById(prefix + 'Bottom'); + if (topEl) topEl.value = half + 'px'; + if (bottomEl) bottomEl.value = (totalPad - half) + 'px'; + } else if (currentAspect < targetAspect) { + // Too tall -> add width (Left/Right) + const targetW = h * targetAspect; + const totalPad = Math.round(targetW - w); + const half = Math.round(totalPad / 2); + const leftEl = document.getElementById(prefix + 'Left'); + const rightEl = document.getElementById(prefix + 'Right'); + if (leftEl) leftEl.value = half + 'px'; + if (rightEl) rightEl.value = (totalPad - half) + 'px'; + } + const uniformEl = document.getElementById(isSb ? 'sbPadUniform' : 'padUniform'); + if (uniformEl) uniformEl.checked = false; + if (isSb) _sbPadUniform = false; } else { - // If it ends with % + // Fixed increment presets let formatted = presetVal; if (presetVal.endsWith('%')) { formatted = parseFloat(presetVal).toFixed(2) + '%'; } else if (presetVal.endsWith('px')) { - formatted = parseFloat(presetVal).toString(); + formatted = parseInt(presetVal) + 'px'; } sides.forEach(side => { @@ -7833,6 +8362,7 @@ const v = document.getElementById(id); if (v) { v.pause(); v.removeAttribute('src'); v.load(); } }); + updateGalleryHighlight(); } function toggleSlideshow() { @@ -8219,11 +8749,65 @@ if (desc || kpts) { poseInfoWrap.style.display = 'block'; poseDescEl.textContent = desc || 'Pose detected'; - _renderPoseToCanvas(poseCanvas, kpts); + _renderPoseToCanvas(poseCanvas, kpts, fname); + + const hudGaze = document.getElementById('lbPoseHudGaze'); + const hudCompl = document.getElementById('lbPoseHudCompl'); + const hudCount = document.getElementById('lbPoseHudCount'); + if (hudGaze) { + const g = fileFacialDirection[fname]; + hudGaze.textContent = g ? `GAZE: ${g.toUpperCase()}` : 'GAZE: -'; + } + if (hudCompl) { + const c = fileAnatomicalCompleteness[fname]; + hudCompl.textContent = c !== null ? (c ? 'BODY: FULL' : 'BODY: PARTIAL') : 'BODY: -'; + } + if (hudCount) { + const n = filePeopleCount[fname]; + hudCount.textContent = n !== null ? `SUBJECTS: ${n}` : 'SUBJECTS: -'; + } } else { poseInfoWrap.style.display = 'none'; } } + + const deepMetaWrap = document.getElementById('lbDeepMetaWrap'); + const deepPeopleCount = document.getElementById('lbDeepPeopleCount'); + const deepCompleteness = document.getElementById('lbDeepCompleteness'); + const deepFacialDirection = document.getElementById('lbDeepFacialDirection'); + const deepObjects = document.getElementById('lbDeepObjects'); + if (deepMetaWrap) { + const count = filePeopleCount[fname]; + const compl = fileAnatomicalCompleteness[fname]; + const faceDir = fileFacialDirection[fname]; + const objs = fileObjects[fname] || []; + const hasDeepMeta = count !== null || compl !== null || faceDir !== null || objs.length > 0; + if (hasDeepMeta) { + deepMetaWrap.style.display = 'block'; + if (deepPeopleCount) { + deepPeopleCount.textContent = count !== null ? `${count} person(s)` : '-'; + } + if (deepCompleteness) { + deepCompleteness.textContent = compl !== null ? (compl ? 'Complete / Full body' : 'Partial / Close-up') : '-'; + } + if (deepFacialDirection) { + deepFacialDirection.textContent = faceDir || '-'; + } + if (deepObjects) { + if (objs.length > 0) { + deepObjects.innerHTML = objs.map(o => { + const tag = typeof o === 'string' ? o : (o.tag || ''); + const score = typeof o === 'object' && o.score ? ` (${Math.round(o.score * 100)}%)` : ''; + return `${escHtml(tag)}${score}`; + }).join(''); + } else { + deepObjects.innerHTML = 'None detected'; + } + } + } else { + deepMetaWrap.style.display = 'none'; + } + } } // Alias for old callsites @@ -8713,7 +9297,7 @@ } const POSE_SKELETON = [[5,7],[7,9],[6,8],[8,10],[11,13],[13,15],[12,14],[14,16],[5,6],[11,12],[5,11],[6,12],[0,1],[0,2],[1,3],[2,4],[0,5],[0,6]]; - function _renderPoseToCanvas(canvas, kptsJson) { + function _renderPoseToCanvas(canvas, kptsJson, fname) { if (!canvas || !kptsJson) return; let kpts; try { kpts = typeof kptsJson === 'string' ? JSON.parse(kptsJson) : kptsJson; } catch(e) { return; } @@ -8727,12 +9311,32 @@ const minX = Math.min(...xs), maxX = Math.max(...xs); const minY = Math.min(...ys), maxY = Math.max(...ys); const w = maxX - minX, h = maxY - minY; - const margin = 10; + const margin = 15; const scale = Math.min((canvas.width - margin*2) / (w || 1), (canvas.height - margin*2) / (h || 1)); const offX = margin - minX * scale + (canvas.width - margin*2 - w * scale) / 2; const offY = margin - minY * scale + (canvas.height - margin*2 - h * scale) / 2; const cx = x => offX + x * scale, cy = y => offY + y * scale; + // Draw a subtle framing/bounding box for anatomical completeness + if (fname) { + const isCompl = fileAnatomicalCompleteness[fname]; + if (isCompl !== undefined && isCompl !== null) { + ctx.save(); + if (isCompl) { + ctx.strokeStyle = 'rgba(16, 185, 129, 0.25)'; // semi-transparent green + ctx.lineWidth = 1; + ctx.setLineDash([4, 4]); + ctx.strokeRect(4, 4, canvas.width - 8, canvas.height - 8); + } else { + ctx.strokeStyle = 'rgba(245, 158, 11, 0.25)'; // semi-transparent amber + ctx.lineWidth = 1; + ctx.setLineDash([2, 4]); + ctx.strokeRect(8, 8, canvas.width - 16, canvas.height - 16); + } + ctx.restore(); + } + } + ctx.lineWidth = 2; ctx.strokeStyle = '#3b82f6'; POSE_SKELETON.forEach(([a, b]) => { const pa = kpts[a], pb = kpts[b]; @@ -8744,6 +9348,43 @@ ctx.beginPath(); ctx.arc(cx(p[0]), cy(p[1]), 2, 0, Math.PI * 2); ctx.fillStyle = '#fff'; ctx.fill(); }); + + // Draw sci-fi gaze and target if nose is visible + if (kpts[0] && kpts[0][2] >= POSE_MIN_SCORE) { + const nx = cx(kpts[0][0]); + const ny = cy(kpts[0][1]); + + // Gaze direction + let gazeDir = fname ? fileFacialDirection[fname] : null; + if (gazeDir) { + gazeDir = gazeDir.toLowerCase(); + ctx.save(); + ctx.beginPath(); + ctx.strokeStyle = '#ef4444'; // Red gaze sight vector + ctx.lineWidth = 1.5; + ctx.setLineDash([2, 2]); + ctx.moveTo(nx, ny); + if (gazeDir.includes('left')) { + ctx.lineTo(nx - 20, ny); + } else if (gazeDir.includes('right')) { + ctx.lineTo(nx + 20, ny); + } else if (gazeDir.includes('up')) { + ctx.lineTo(nx, ny - 20); + } else if (gazeDir.includes('down')) { + ctx.lineTo(nx, ny + 20); + } else { // 'front' + // concentric circles for front gaze + ctx.setLineDash([]); + ctx.strokeStyle = 'rgba(239, 68, 68, 0.4)'; + ctx.arc(nx, ny, 8, 0, Math.PI * 2); + ctx.stroke(); + ctx.beginPath(); + ctx.arc(nx, ny, 4, 0, Math.PI * 2); + } + ctx.stroke(); + ctx.restore(); + } + } } function _poseHitTest(mx, my) { @@ -8852,6 +9493,7 @@ if (btn) { btn.disabled = false; btn.textContent = 'Generate ▸'; } if (job.status === 'done') { showToast('Pose generation done!', 'success'); + _followLatestFilename = job.output; _followLatestGid = lbCurrentGid; refreshNow(true); } else { @@ -9288,13 +9930,17 @@ + `` + `` + `` - + `` + + `` + + `` + + `` + + `` + + `` + `` + `Custom:` - + numInput('padTop','↑') - + numInput('padRight','→') - + numInput('padBottom','↓') - + numInput('padLeft','←') + + numInput('padTop','Top:') + + numInput('padRight','Right:') + + numInput('padBottom','Bottom:') + + numInput('padLeft','Left:') + `` + `+10% + + + + @@ -11565,7 +12229,8 @@ function showPromptSuggest(el, boxId = 'sbPromptSuggest') { const box = document.getElementById(boxId); if (!box) return; - const hist = JSON.parse(localStorage.getItem('batchPromptHistory') || '[]'); + const histKey = boxId === 'scenePromptSuggest' ? 'scenePromptHistory' : 'batchPromptHistory'; + const hist = JSON.parse(localStorage.getItem(histKey) || '[]'); const q = (el.value || '').trim().toLowerCase(); const matches = (q ? hist.filter(p => p.toLowerCase().includes(q) && p.toLowerCase() !== q) : hist).slice(0, 40); if (!matches.length) { box.style.display = 'none'; return; } @@ -11844,11 +12509,12 @@ btn.disabled = true; btn.innerHTML = ' Refining…'; + const filename = (lbIdx >= 0 && lbNames[lbIdx]) ? lbNames[lbIdx] : null; try { const r = await fetch(`${API}/refine-prompt`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ prompt }) + body: JSON.stringify({ prompt, filename }) }); if (r.ok) { const { refined } = await r.json(); @@ -11901,11 +12567,12 @@ btn.disabled = true; btn.innerHTML = ' Refining…'; + const filename = (lbIdx >= 0 && lbNames[lbIdx]) ? lbNames[lbIdx] : null; try { const r = await fetch(`${API}/refine-prompt`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ prompt }) + body: JSON.stringify({ prompt, filename }) }); if (r.ok) { const { refined } = await r.json(); @@ -12335,6 +13002,7 @@ if (textEl) textEl.textContent = `Done! ${job.output || ''}`; if (btn) btn.textContent = 'Done ✓'; showToast(`Faceswap complete: ${job.output || ''}`, 'success'); + _followLatestFilename = job.output; _followLatestGid = lbCurrentGid; refreshNow(true); } else { @@ -12449,6 +13117,70 @@ // ---- scenery sidebar tab ---- let _sceneVideo = null, _sceneDuration = 0, _sceneFrameBytes = null, _sceneJobPollTimer = null; + let _sceneLastKey = null; + + function getActiveSceneKey() { + const bg = _sceneRefs[0]; + if (!bg) return null; + if (bg.kind === 'wireframe') return 'wf:' + bg.name; + if (bg.kind === 'file') return 'file:' + bg.name; + if (bg.kind === 'bytes') { + if (bg.filename) return 'lib:' + bg.filename; + return 'bytes:' + bg.data.length + '_' + bg.data.substring(0, 32); + } + return null; + } + + function getScenePrompt(key) { + if (!key) return null; + const prompts = JSON.parse(localStorage.getItem('sceneSavedPrompts') || '{}'); + return prompts[key] || null; + } + + function saveScenePrompt(prompt) { + const key = getActiveSceneKey(); + if (!key || !prompt) return; + const prompts = JSON.parse(localStorage.getItem('sceneSavedPrompts') || '{}'); + prompts[key] = prompt; + localStorage.setItem('sceneSavedPrompts', JSON.stringify(prompts)); + } + + function saveScenePromptManual() { + const el = document.getElementById('scenePromptInput'); + if (!el) return; + const prompt = el.value.trim(); + if (!prompt) { + showToast('Please type a prompt first', 'info'); + return; + } + saveScenePrompt(prompt); + showToast('Prompt saved for this background', 'success'); + renderSidebarScenery(); + } + + function clearSavedScenePrompt() { + const key = getActiveSceneKey(); + if (!key) return; + if (!confirm('Clear the saved prompt for this background?')) return; + const prompts = JSON.parse(localStorage.getItem('sceneSavedPrompts') || '{}'); + delete prompts[key]; + localStorage.setItem('sceneSavedPrompts', JSON.stringify(prompts)); + showToast('Saved prompt removed', 'success'); + renderSidebarScenery(); + } + + function useSavedScenePrompt() { + const key = getActiveSceneKey(); + const savedPrompt = getScenePrompt(key); + if (!savedPrompt) return; + const el = document.getElementById('scenePromptInput'); + if (el) { + el.value = savedPrompt; + autoGrowPrompt(el); + renderSidebarScenery(); + showToast('Saved scene prompt loaded', 'success'); + } + } let _sceneGridOpen = false; // whether the video picker grid is expanded let _sceneLibOpen = false; // whether "previous scenery" library panel is expanded @@ -12466,7 +13198,16 @@ if (!panel) return; const savedScrollTop = panel.scrollTop; // Preserve a typed prompt across the frequent re-renders (slot changes). - const prevScenePrompt = document.getElementById('scenePromptInput')?.value || ''; + let prevScenePrompt = document.getElementById('scenePromptInput')?.value || ''; + + const activeSceneKey = getActiveSceneKey(); + if (activeSceneKey !== _sceneLastKey) { + _sceneLastKey = activeSceneKey; + const savedPrompt = getScenePrompt(activeSceneKey); + if (savedPrompt !== null) { + prevScenePrompt = savedPrompt; + } + } // Auto-fill the Person slot (image2) once per group with the current image. const personName = _fsModelFilename || lbNames[lbIdx] || ''; if (!_scenePersonInit && !_sceneRefs[1] && personName) { @@ -12655,7 +13396,34 @@ onkeydown="if(event.key==='Escape')hidePromptSuggest('scenePromptSuggest')" style="resize:none;max-height:300px;overflow-y:auto;line-height:1.4;font-family:inherit"> - + `; + + if (activeSceneKey) { + const savedPrompt = getScenePrompt(activeSceneKey); + if (savedPrompt) { + html += ` +
+
+ Saved Prompt for this Background: + Delete +
+
${escHtml(savedPrompt)}
+
+ + +
+
`; + } else { + html += ` +
+ +
`; + } + } + + html += `