From db11bc59d1296da3a26659e47099ed07e72e270c Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Tue, 26 Mar 2024 09:02:30 -0400 Subject: [PATCH] gallery optimizations --- javascript/gallery.js | 30 ++++++++++--------- modules/api/gallery.py | 65 +++++++++++++++++++++++------------------- wiki | 2 +- 3 files changed, 54 insertions(+), 43 deletions(-) diff --git a/javascript/gallery.js b/javascript/gallery.js index 2306d0c5c..96d0773a8 100644 --- a/javascript/gallery.js +++ b/javascript/gallery.js @@ -108,12 +108,12 @@ async function delayFetchThumb(fn) { } class GalleryFile extends HTMLElement { - constructor({ folder, file, size, mtime }) { + constructor(folder, file) { super(); this.folder = decodeURI(folder); this.name = decodeURI(file); - this.size = size; - this.mtime = new Date(1000 * mtime); + this.size = 0; + this.mtime = 0; this.hash = undefined; this.exif = ''; this.width = 0; @@ -163,6 +163,8 @@ class GalleryFile extends HTMLElement { this.exif = cache.exif; this.width = cache.width; this.height = cache.height; + this.size = cache.size; + this.mtime = new Date(1000 * cache.mtime); } else { try { const json = await delayFetchThumb(this.src); @@ -173,6 +175,8 @@ class GalleryFile extends HTMLElement { this.exif = json.exif; this.width = json.width; this.height = json.height; + this.size = json.size; + this.mtime = new Date(1000 * json.mtime); await idbAdd({ hash: this.hash, folder: this.folder, @@ -324,37 +328,37 @@ async function fetchFiles(evt) { // fetch file-by-file list over websockets if (ws && ws.readyState === WebSocket.OPEN) ws.close(); // abort previous request ws = new WebSocket(`${url}/sdapi/v1/browser/files`); await wsConnect(ws); - let numFiles = 0; el.status.innerText = `Folder | ${evt.target.name}`; const t0 = performance.now(); + let numFiles = 0; let t1 = performance.now(); - let lastDir; let fragment = document.createDocumentFragment(); - ws.onmessage = (event) => { // time is 20% list 80% create item + ws.onmessage = (event) => { numFiles++; t1 = performance.now(); - if (event.data === '#END#') { + const data = event.data.split('##F##'); + if (data[0] === '#END#') { ws.close(); } else { - const json = JSON.parse(event.data); - const file = new GalleryFile(json); + const file = new GalleryFile(data[0], data[1]); fragment.appendChild(file); if (numFiles % 100 === 0) { + el.status.innerText = `Folder | ${evt.target.name} | ${numFiles.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`; el.files.appendChild(fragment); fragment = document.createDocumentFragment(); } - addSeparators(); - el.status.innerText = `Folder | ${evt.target.name} | ${numFiles.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`; } }; ws.onclose = (event) => { el.files.appendChild(fragment); - // log('gallery ws file enum', event); + log(`gallery: folder=${evt.target.name} num=${numFiles} time=${Math.floor(t1 - t0)}ms`); + el.status.innerText = `Folder | ${evt.target.name} | ${numFiles.toLocaleString()} images | ${Math.floor(t1 - t0).toLocaleString()}ms`; + addSeparators(); }; ws.onerror = (event) => { log('gallery ws error', event); }; - ws.send(evt.target.name); + ws.send(encodeURI(evt.target.name)); } async function pruneImages() { diff --git a/modules/api/gallery.py b/modules/api/gallery.py index 55afb1500..49119473d 100644 --- a/modules/api/gallery.py +++ b/modules/api/gallery.py @@ -76,6 +76,7 @@ def register_api(app: FastAPI): # register api def get_video_thumbnail(filepath): from modules.ui_control_helpers import get_video_params try: + stat = os.stat(filepath) frames, fps, duration, width, height, codec, frame = get_video_params(filepath, capture=True) h = shared.opts.extra_networks_card_size w = shared.opts.extra_networks_card_size if shared.opts.browser_fixed_width else width * h // height @@ -90,12 +91,41 @@ def register_api(app: FastAPI): # register api 'data': data_url, 'width': width, 'height': height, + 'size': stat.st_size, + 'mtime': stat.st_mtime, } return content except Exception as e: shared.log.error(f'Gallery video: file="{filepath}" {e}') return {} + def get_image_thumbnail(filepath): + try: + stat = os.stat(filepath) + image = Image.open(filepath) + geninfo, _items = images.read_info_from_image(image) + h = shared.opts.extra_networks_card_size + w = shared.opts.extra_networks_card_size if shared.opts.browser_fixed_width else image.width * h // image.height + width, height = image.width, image.height + image = image.convert('RGB') + image.thumbnail((w, h), Image.Resampling.HAMMING) + buffered = io.BytesIO() + image.save(buffered, format='jpeg') + data_url = f'data:image/jpeg;base64,{base64.b64encode(buffered.getvalue()).decode("ascii")}' + image.close() + content = { + 'exif': geninfo, + 'data': data_url, + 'width': width, + 'height': height, + 'size': stat.st_size, + 'mtime': stat.st_mtime, + } + return content + except Exception as e: + shared.log.error(f'Gallery image: file="{filepath}" {e}') + return {} + @app.get('/sdapi/v1/browser/folders', response_model=List[str]) def get_folders(): folders = [shared.opts.data.get(f, '') for f in OPTS_FOLDERS] @@ -114,29 +144,11 @@ def register_api(app: FastAPI): # register api @app.get("/sdapi/v1/browser/thumb", response_model=dict) async def get_thumb(file: str): try: - decoded = unquote(file) + decoded = unquote(file).replace('%3A', ':') if decoded.lower().endswith('.mp4'): - content = get_video_thumbnail(decoded) - return JSONResponse(content=content) + return JSONResponse(content=get_video_thumbnail(decoded)) else: - image = Image.open(decoded) - geninfo, _items = images.read_info_from_image(image) - h = shared.opts.extra_networks_card_size - w = shared.opts.extra_networks_card_size if shared.opts.browser_fixed_width else image.width * h // image.height - width, height = image.width, image.height - image = image.convert('RGB') - image.thumbnail((w, h), Image.Resampling.HAMMING) - buffered = io.BytesIO() - image.save(buffered, format='jpeg') - data_url = f'data:image/jpeg;base64,{base64.b64encode(buffered.getvalue()).decode("ascii")}' - image.close() - content = { - 'exif': geninfo, - 'data': data_url, - 'width': width, - 'height': height, - } - return JSONResponse(content=content) + return JSONResponse(content=get_image_thumbnail(decoded)) except Exception as e: shared.log.error(f'Gallery: {file} {e}') content = { 'error': str(e) } @@ -147,19 +159,14 @@ def register_api(app: FastAPI): # register api try: await manager.connect(ws) folder = await ws.receive_text() + folder = unquote(folder).replace('%3A', ':') t0 = time.time() numFiles = 0 for f in files_cache.directory_files(folder, recursive=True): numFiles += 1 file = os.path.relpath(f, folder) - stat = os.stat(f) - dct = { - 'folder': quote(folder), - 'file': quote(file), - 'size': stat.st_size, - 'mtime': stat.st_mtime, - } - await manager.send(ws, dct) + msg = quote(folder) + '##F##' + quote(file) + await manager.send(ws, msg) await manager.send(ws, '#END#') t1 = time.time() shared.log.debug(f'Gallery: folder="{folder}" files={numFiles} time={t1-t0:.3f}') diff --git a/wiki b/wiki index 1dabbb019..108b18396 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 1dabbb019292c4bcfa1a94215a86f4aab4650ba9 +Subproject commit 108b183962e485d39fea47e65a1ccd3092cad7b6