# Image Grabber sketch This is a shallow dive for a image analyst tool to grab imagery from a website, saving to a local folder and documenting the source url and date in a meta.json file.  **background:** Ailson bought an piece of artwork at Spanish Market from the artist, Brandon-Maldonado. I visited his site and purchased another. While there, I wanted to zoom in on his images beyond what the web UI allowed. I used this as an excuse to vibe code a image grabber to local file system and then a second Ken Burns type of slideshow. ## Image Downloader Bookmarklets We explored how to create two powerful bookmarklets for downloading all images from a webpage, leveraging the **File System Access API** for direct saving to a local folder and creating a `metadata.json` file for persistence. The steps we followed were: - **Generic Downloader:** - Iterated through all `` elements on the page. - Saved each image directly to a user-selected folder using `showDirectoryPicker()`. - Created a `metadata.json` file containing the original URLs and timestamps of the downloads. - **Squarespace High-Res Downloader:** - Tuned the logic to find `data-src` or `data-image` attributes in Squarespace galleries. - Replaced image URLs with `?format=original` to fetch the highest resolution. - Saved all hi-res images to a local folder along with a `metadata.json` describing the files. - **File System API and Meta File:** - We used `window.showDirectoryPicker()` to request a folder once, avoiding multiple dialogs. - The bookmarklet wrote both the image files and a `metadata.json` to this folder for future reference. - This provides a level of persistence and organization similar to a lightweight file manager. ### **Generic Downloader Bookmarklet (Expanded)** (async()=>{ try { const imgs = [...document.images]; if (imgs.length === 0) return alert('No images found.'); const dirHandle = await window.showDirectoryPicker(); const meta = []; for (let i = 0; i < imgs.length; i++) { try { const img = imgs[i]; const res = await fetch(img.src); const blob = await res.blob(); const ext = (img.src.split('.').pop().split('?')[0] || 'png').split('/')[0]; const fileHandle = await dirHandle.getFileHandle(`image-${i}.${ext}`, { create: true }); const writable = await fileHandle.createWritable(); await writable.write(blob); await writable.close(); meta.push({ index: i, url: img.src, dateCaptured: new Date().toISOString() }); } catch (err) { console.error('Failed to save image', err); } } const metaHandle = await dirHandle.getFileHandle('metadata.json', { create: true }); const metaWritable = await metaHandle.createWritable(); await metaWritable.write(JSON.stringify(meta, null, 2)); await metaWritable.close(); alert('All images saved with metadata.json'); } catch (err) { console.error('Error:', err); alert('Something went wrong. Check console.'); } })(); **Single-line version:** javascript:(async()=>{try{const imgs=[...document.images];if(imgs.length===0)return alert('No images found.');const dirHandle=await window.showDirectoryPicker();const meta=[];for(let i=0;i{ try { const imgs = [...document.querySelectorAll('img[data-src], img[data-image], img[src]')]; if (imgs.length === 0) return alert('No Squarespace images found.'); const dirHandle = await window.showDirectoryPicker(); const meta = []; for (let i = 0; i < imgs.length; i++) { try { const img = imgs[i]; let url = img.getAttribute('data-src') || img.getAttribute('data-image') || img.src; url = url.replace(/\?.*$/, '') + '?format=original'; const res = await fetch(url); const blob = await res.blob(); const ext = (url.split('.').pop().split('?')[0] || 'jpg').split('/')[0]; const fileHandle = await dirHandle.getFileHandle(`image-${i}.${ext}`, { create: true }); const writable = await fileHandle.createWritable(); await writable.write(blob); await writable.close(); meta.push({ index: i, url: url, dateCaptured: new Date().toISOString(), originalDimensions: img.getAttribute('data-image-dimensions') || null }); } catch (err) { console.error('Failed to save image', err); } } const metaHandle = await dirHandle.getFileHandle('metadata.json', { create: true }); const metaWritable = await metaHandle.createWritable(); await metaWritable.write(JSON.stringify(meta, null, 2)); await metaWritable.close(); alert('All Squarespace images saved with metadata.json.'); } catch (err) { console.error(err); alert('Error occurred. Check console.'); } })(); **Single-line version:** javascript:(async()=>{try{const imgs=[...document.querySelectorAll('img[data-src],img[data-image],img[src]')];if(imgs.length===0)return alert('No Squarespace images found.');const dirHandle=await window.showDirectoryPicker();const meta=[];for(let i=0;i{try{const%20imgs=[...document.querySelectorAll('img[data-src],img[data-image],img[src]')];if(imgs.length===0)return%20alert('No%20Squarespace%20images%20found.');const%20dirHandle=await%20window.showDirectoryPicker();const%20meta=[];for(let%20i=0;i{try%7Bconst%20imgs%3D%5B...document.querySelectorAll('img%5Bdata-src%5D%2Cimg%5Bdata-image%5D%2Cimg%5Bsrc%5D')%5D%3Bif(imgs.length%3D%3D%3D0)return%20alert('No%20Squarespace%20images%20found.')%3Bconst%20dirHandle%3Dawait%20window.showDirectoryPicker()%3Bconst%20meta%3D%5B%5D%3Bfor(let%20i%3D0%3Bi%3Cimgs.length%3Bi%2B%2B)%7Btry%7Bconst%20img%3Dimgs%5Bi%5D%3Blet%20url%3Dimg.getAttribute('data-src')%7C%7Cimg.getAttribute('data-image')%7C%7Cimg.src%3Burl%3Durl.replace(%2F%5C%3F.*%24%2F%2C'')%2B'%3Fformat%3Doriginal'%3Bconst%20res%3Dawait%20fetch(url)%3Bconst%20blob%3Dawait%20res.blob()%3Bconst%20ext%3D(url.split('.') .pop().split('%3F')%5B0%5D%7C%7C'jpg').split('%2F')%5B0%5D%3Bconst%20fileHandle%3Dawait%20dirHandle.getFileHandle(%60image-%24%7Bi%7D.%24%7Bext%7D%60%2C%7Bcreate%3Atrue%7D)%3Bconst%20writable%3Dawait%20fileHandle.createWritable()%3Bawait%20writable.write(blob)%3Bawait%20writable.close()%3Bmeta.push(%7Bindex%3Ai%2Curl%3Aurl%2CdateCaptured%3Anew%20Date().toISOString()%2CoriginalDimensions%3Aimg.getAttribute('data-image-dimensions')%7C%7Cnull%7D)%3B%7Dcatch(err)%7Bconsole.error('Failed%20to%20save%20image'%2Cerr)%3B%7D%7Dconst%20metaHandle%3Dawait%20dirHandle.getFileHandle('metadata.json'%2C%7Bcreate%3Atrue%7D)%3Bconst%20metaWritable%3Dawait%20metaHandle.createWritable()%3Bawait%20metaWritable.write(JSON.stringify(meta%2Cnull%2C2))%3Bawait%20metaWritable.close()%3Balert('All%20Squarespace%20images%20saved%20with%20metadata.json.')%3B%7Dcatch(err)%7Bconsole.error(err)%3Balert('Error%20occurred.%20Check%20console.')%3B%7D%7D)()%3B)