# 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)