feat: compress EVERYTHING!

This commit is contained in:
max_richter 2023-11-13 14:09:38 +01:00
parent 9f55b88b50
commit 9c395d82f3
35 changed files with 462 additions and 97 deletions

106
bin/compress_s3.js Normal file
View File

@ -0,0 +1,106 @@
import { Client } from "minio";
import sharp from "sharp"
import {config} from "dotenv"
config()
// Retrieve MinIO details from environment variables
const minioEndpoint = process.env["S3_ENDPOINT_URL"] || "your-minio-endpoint";
const minioAccessKey = process.env["S3_ACCESS_KEY"] || "your-access-key";
const minioSecretKey = process.env["S3_SECRET_ACCESS_KEY"] || "your-secret-key";
const minioBucketName = "silvester23";
const minioClient = new Client({
endPoint: minioEndpoint,
accessKey: minioAccessKey,
secretKey: minioSecretKey,
});
const objects = [];
const objectStream = minioClient.listObjects(minioBucketName);
for await (const obj of objectStream) {
objects.push(obj);
}
async function compressObject(pngBuffer, fileName, format) {
const newFilePath = `${fileName.slice(0, -4)}.${format}`;
let buffer;
switch (format) {
case "webp":
buffer = await sharp(pngBuffer).webp({ quality: 80 }).toBuffer();
break;
case "jpg":
buffer = await sharp(pngBuffer).jpeg({ quality: 80 }).toBuffer();
break;
case "avif":
buffer = await sharp(pngBuffer).avif({ quality: 80 }).toBuffer();
break;
default:
throw new Error(`Unknown format ${format}`);
}
// Upload the JPG buffer back to the same MinIO bucket
await minioClient.putObject(minioBucketName, newFilePath, buffer, {
'Content-Type': 'image/' + format,
});
console.log(`Uploaded ${newFilePath} to MinIO`);
}
async function fetchBuffer(objectName) {
const chunks = [];
return new Promise((res, rej) => {
minioClient.getObject(minioBucketName,objectName, function (err, dataStream) {
if (err) {
return console.log(err)
}
dataStream.on('data', function (chunk) {
chunks.push(chunk);
})
dataStream.on('end', function () {
res(Buffer.concat(chunks))
})
dataStream.on('error', function (err) {
console.log(err)
rej(err)
})
})
})
}
const pngs = objects.filter((o) => o.name.endsWith(".png"));
console.log(`Found ${pngs.length} PNGs`);
for (const obj of pngs) {
const pngFilePath = obj.name;
const jpgFilePath = pngFilePath.slice(0, -4) + ".jpg";
const avifFilePath = pngFilePath.slice(0, -4) + ".avif";
const webpFilePath = pngFilePath.slice(0, -4) + ".webp";
const hasJpg = objects.some((o) => o.name === jpgFilePath);
const hasAvif = objects.some((o) => o.name === avifFilePath);
const hasWebp = objects.some((o) => o.name === webpFilePath);
if(hasAvif && hasJpg && hasWebp) {
continue;
}
const pngBuffer = await fetchBuffer(pngFilePath);
if(!hasJpg) {
await compressObject(pngBuffer, pngFilePath, "jpg");
}
if(!hasAvif) {
await compressObject(pngBuffer, pngFilePath, "avif");
}
if(!hasWebp) {
await compressObject(pngBuffer, pngFilePath, "webp");
}
}

View File

@ -1,55 +0,0 @@
import { Client } from "npm:minio";
import Jimp from 'npm:jimp';
import { config } from "https://deno.land/x/dotenv/mod.ts";
config({ export: true });
interface MinioObject {
name: string;
}
// Retrieve MinIO details from environment variables
const minioEndpoint = Deno.env.get("S3_ENDPOINT_URL") || "your-minio-endpoint";
const minioAccessKey = Deno.env.get("S3_ACCESS_KEY") || "your-access-key";
const minioSecretKey = Deno.env.get("S3_SECRET_ACCESS_KEY") || "your-secret-key";
const minioBucketName = "silvester23";
const minioClient = new Client({
endPoint: minioEndpoint,
accessKey: minioAccessKey,
secretKey: minioSecretKey,
});
const objects: MinioObject[] = [];
const objectStream = minioClient.listObjects(minioBucketName);
for await (const obj of objectStream) {
objects.push({
name: obj.name,
});
}
// Process and upload images
for (const obj of objects) {
const pngFilePath = obj.name;
const jpgFilePath = `${pngFilePath.slice(0, -4)}.jpg`;
// Check if the PNG file exists and there is no corresponding JPG file
if (!objects.some((o) => o.name === jpgFilePath)) {
// Download the PNG file from MinIO
const pngBuffer = await minioClient.getObject(minioBucketName, pngFilePath);
console.log(`Downloaded ${pngFilePath} from MinIO`);
// Use Jimp to compress and convert the PNG to JPG
const image = await Jimp.read(`http://s3-api.app.max-richter.dev/silvester23/${pngFilePath}`);
const jpgBuffer = await image.quality(80).getBufferAsync(Jimp.MIME_JPEG);
// Upload the JPG buffer back to the same MinIO bucket
await minioClient.putObject(minioBucketName, jpgFilePath, jpgBuffer, {
'Content-Type': 'image/jpeg',
});
console.log(`Uploaded ${jpgFilePath} to MinIO`);
}
}

View File

@ -17,6 +17,7 @@
"@sveltejs/kit": "^1.27.5", "@sveltejs/kit": "^1.27.5",
"@typescript-eslint/eslint-plugin": "^6.10.0", "@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0", "@typescript-eslint/parser": "^6.10.0",
"dotenv": "^16.3.1",
"eslint": "^8.53.0", "eslint": "^8.53.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"eslint-plugin-svelte": "^2.35.0", "eslint-plugin-svelte": "^2.35.0",
@ -34,6 +35,7 @@
"jimp": "^0.22.10", "jimp": "^0.22.10",
"minio": "^7.1.3", "minio": "^7.1.3",
"openai": "^4.17.4", "openai": "^4.17.4",
"pocketbase": "^0.19.0" "pocketbase": "^0.19.0",
"sharp": "^0.32.6"
} }
} }

View File

@ -20,6 +20,9 @@ dependencies:
pocketbase: pocketbase:
specifier: ^0.19.0 specifier: ^0.19.0
version: 0.19.0 version: 0.19.0
sharp:
specifier: ^0.32.6
version: 0.32.6
devDependencies: devDependencies:
'@sveltejs/adapter-node': '@sveltejs/adapter-node':
@ -34,6 +37,9 @@ devDependencies:
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: ^6.10.0 specifier: ^6.10.0
version: 6.10.0(eslint@8.53.0)(typescript@5.2.2) version: 6.10.0(eslint@8.53.0)(typescript@5.2.2)
dotenv:
specifier: ^16.3.1
version: 16.3.1
eslint: eslint:
specifier: ^8.53.0 specifier: ^8.53.0
version: 8.53.0 version: 8.53.0
@ -1171,6 +1177,10 @@ packages:
dequal: 2.0.3 dequal: 2.0.3
dev: true dev: true
/b4a@1.6.4:
resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==}
dev: false
/balanced-match@1.0.2: /balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true dev: true
@ -1192,6 +1202,14 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
dependencies:
buffer: 5.7.1
inherits: 2.0.4
readable-stream: 3.6.2
dev: false
/block-stream2@2.1.0: /block-stream2@2.1.0:
resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==} resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==}
dependencies: dependencies:
@ -1290,6 +1308,10 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true dev: true
/chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
dev: false
/code-red@1.0.4: /code-red@1.0.4:
resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==} resolution: {integrity: sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==}
dependencies: dependencies:
@ -1305,11 +1327,24 @@ packages:
engines: {node: '>=7.0.0'} engines: {node: '>=7.0.0'}
dependencies: dependencies:
color-name: 1.1.4 color-name: 1.1.4
dev: true
/color-name@1.1.4: /color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
dependencies:
color-name: 1.1.4
simple-swizzle: 0.2.2
dev: false
/color@4.2.3:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
dependencies:
color-convert: 2.0.1
color-string: 1.9.1
dev: false
/combined-stream@1.0.8: /combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
@ -1374,6 +1409,18 @@ packages:
engines: {node: '>=0.10'} engines: {node: '>=0.10'}
dev: false dev: false
/decompress-response@6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'}
dependencies:
mimic-response: 3.1.0
dev: false
/deep-extend@0.6.0:
resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
engines: {node: '>=4.0.0'}
dev: false
/deep-is@0.1.4: /deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dev: true dev: true
@ -1407,6 +1454,11 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/detect-libc@2.0.2:
resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
engines: {node: '>=8'}
dev: false
/devalue@4.3.2: /devalue@4.3.2:
resolution: {integrity: sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==} resolution: {integrity: sha512-KqFl6pOgOW+Y6wJgu80rHpo2/3H07vr8ntR9rkkFIRETewbf5GaYYcakYfiKz89K+sLsuPkQIZaXDMjUObZwWg==}
dev: true dev: true
@ -1436,12 +1488,23 @@ packages:
resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==}
dev: false dev: false
/dotenv@16.3.1:
resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==}
engines: {node: '>=12'}
dev: true
/ecdsa-sig-formatter@1.0.11: /ecdsa-sig-formatter@1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
dependencies: dependencies:
safe-buffer: 5.2.1 safe-buffer: 5.2.1
dev: false dev: false
/end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
dependencies:
once: 1.4.0
dev: false
/es6-promise@3.3.1: /es6-promise@3.3.1:
resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==}
dev: true dev: true
@ -1644,6 +1707,11 @@ packages:
resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==} resolution: {integrity: sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==}
dev: false dev: false
/expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
dev: false
/extend@3.0.2: /extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
dev: false dev: false
@ -1652,6 +1720,10 @@ packages:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
dev: true dev: true
/fast-fifo@1.3.2:
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
dev: false
/fast-glob@3.3.2: /fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'} engines: {node: '>=8.6.0'}
@ -1760,6 +1832,10 @@ packages:
web-streams-polyfill: 4.0.0-beta.3 web-streams-polyfill: 4.0.0-beta.3
dev: false dev: false
/fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
dev: false
/fs.realpath@1.0.0: /fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true dev: true
@ -1815,6 +1891,10 @@ packages:
omggif: 1.0.10 omggif: 1.0.10
dev: false dev: false
/github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
dev: false
/glob-parent@5.1.2: /glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -2039,6 +2119,10 @@ packages:
/inherits@2.0.4: /inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
/ini@1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
dev: false
/ipaddr.js@2.1.0: /ipaddr.js@2.1.0:
resolution: {integrity: sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==} resolution: {integrity: sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
@ -2052,6 +2136,10 @@ packages:
has-tostringtag: 1.0.0 has-tostringtag: 1.0.0
dev: false dev: false
/is-arrayish@0.3.2:
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
dev: false
/is-binary-path@2.1.0: /is-binary-path@2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -2279,7 +2367,6 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dependencies: dependencies:
yallist: 4.0.0 yallist: 4.0.0
dev: true
/magic-string@0.27.0: /magic-string@0.27.0:
resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
@ -2338,6 +2425,11 @@ packages:
hasBin: true hasBin: true
dev: false dev: false
/mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
dev: false
/min-document@2.19.0: /min-document@2.19.0:
resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==} resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==}
dependencies: dependencies:
@ -2364,7 +2456,6 @@ packages:
/minimist@1.2.8: /minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
dev: true
/minio@7.1.3: /minio@7.1.3:
resolution: {integrity: sha512-xPrLjWkTT5E7H7VnzOjF//xBp9I40jYB4aWhb2xTFopXXfw+Wo82DDWngdUju7Doy3Wk7R8C4LAgwhLHHnf0wA==} resolution: {integrity: sha512-xPrLjWkTT5E7H7VnzOjF//xBp9I40jYB4aWhb2xTFopXXfw+Wo82DDWngdUju7Doy3Wk7R8C4LAgwhLHHnf0wA==}
@ -2386,6 +2477,10 @@ packages:
xml2js: 0.5.0 xml2js: 0.5.0
dev: false dev: false
/mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
dev: false
/mkdirp@0.5.6: /mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true hasBin: true
@ -2416,10 +2511,25 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/napi-build-utils@1.0.2:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
dev: false
/natural-compare@1.4.0: /natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true dev: true
/node-abi@3.51.0:
resolution: {integrity: sha512-SQkEP4hmNWjlniS5zdnfIXTk1x7Ome85RDzHlTbBtzE97Gfwz/Ipw4v/Ryk20DWIy3yCNVLVlGKApCnmvYoJbA==}
engines: {node: '>=10'}
dependencies:
semver: 7.5.4
dev: false
/node-addon-api@6.1.0:
resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
dev: false
/node-domexception@1.0.0: /node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'} engines: {node: '>=10.5.0'}
@ -2454,7 +2564,6 @@ packages:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
dev: true
/openai@4.17.4: /openai@4.17.4:
resolution: {integrity: sha512-ThRFkl6snLbcAKS58St7N3CaKuI5WdYUvIjPvf4s+8SdymgNtOfzmZcZXVcCefx04oKFnvZJvIcTh3eAFUUhAQ==} resolution: {integrity: sha512-ThRFkl6snLbcAKS58St7N3CaKuI5WdYUvIjPvf4s+8SdymgNtOfzmZcZXVcCefx04oKFnvZJvIcTh3eAFUUhAQ==}
@ -2652,6 +2761,25 @@ packages:
source-map-js: 1.0.2 source-map-js: 1.0.2
dev: true dev: true
/prebuild-install@7.1.1:
resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
engines: {node: '>=10'}
hasBin: true
dependencies:
detect-libc: 2.0.2
expand-template: 2.0.3
github-from-package: 0.0.0
minimist: 1.2.8
mkdirp-classic: 0.5.3
napi-build-utils: 1.0.2
node-abi: 3.51.0
pump: 3.0.0
rc: 1.2.8
simple-get: 4.0.1
tar-fs: 2.1.1
tunnel-agent: 0.6.0
dev: false
/prelude-ls@1.2.1: /prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -2678,6 +2806,13 @@ packages:
engines: {node: '>= 0.6.0'} engines: {node: '>= 0.6.0'}
dev: false dev: false
/pump@3.0.0:
resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==}
dependencies:
end-of-stream: 1.4.4
once: 1.4.0
dev: false
/punycode@2.3.1: /punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -2704,6 +2839,20 @@ packages:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true dev: true
/queue-tick@1.0.1:
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
dev: false
/rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
dependencies:
deep-extend: 0.6.0
ini: 1.3.8
minimist: 1.2.8
strip-json-comments: 2.0.1
dev: false
/readable-stream@3.6.2: /readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -2808,7 +2957,6 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
lru-cache: 6.0.0 lru-cache: 6.0.0
dev: true
/set-cookie-parser@2.6.0: /set-cookie-parser@2.6.0:
resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==} resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==}
@ -2824,6 +2972,21 @@ packages:
has-property-descriptors: 1.0.1 has-property-descriptors: 1.0.1
dev: false dev: false
/sharp@0.32.6:
resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==}
engines: {node: '>=14.15.0'}
requiresBuild: true
dependencies:
color: 4.2.3
detect-libc: 2.0.2
node-addon-api: 6.1.0
prebuild-install: 7.1.1
semver: 7.5.4
simple-get: 4.0.1
tar-fs: 3.0.4
tunnel-agent: 0.6.0
dev: false
/shebang-command@2.0.0: /shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -2844,6 +3007,24 @@ packages:
object-inspect: 1.13.1 object-inspect: 1.13.1
dev: false dev: false
/simple-concat@1.0.1:
resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
dev: false
/simple-get@4.0.1:
resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
dependencies:
decompress-response: 6.0.0
once: 1.4.0
simple-concat: 1.0.1
dev: false
/simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
dependencies:
is-arrayish: 0.3.2
dev: false
/sirv@2.0.3: /sirv@2.0.3:
resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
@ -2878,6 +3059,13 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: false dev: false
/streamx@2.15.4:
resolution: {integrity: sha512-uSXKl88bibiUCQ1eMpItRljCzDENcDx18rsfDmV79r0e/ThfrAwxG4Y2FarQZ2G4/21xcOKmFFd1Hue+ZIDwHw==}
dependencies:
fast-fifo: 1.3.2
queue-tick: 1.0.1
dev: false
/strict-uri-encode@2.0.0: /strict-uri-encode@2.0.0:
resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -2903,6 +3091,11 @@ packages:
min-indent: 1.0.1 min-indent: 1.0.1
dev: true dev: true
/strip-json-comments@2.0.1:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
dev: false
/strip-json-comments@3.1.1: /strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -3052,6 +3245,42 @@ packages:
periscopic: 3.1.0 periscopic: 3.1.0
dev: true dev: true
/tar-fs@2.1.1:
resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
dependencies:
chownr: 1.1.4
mkdirp-classic: 0.5.3
pump: 3.0.0
tar-stream: 2.2.0
dev: false
/tar-fs@3.0.4:
resolution: {integrity: sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==}
dependencies:
mkdirp-classic: 0.5.3
pump: 3.0.0
tar-stream: 3.1.6
dev: false
/tar-stream@2.2.0:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
dependencies:
bl: 4.1.0
end-of-stream: 1.4.4
fs-constants: 1.0.0
inherits: 2.0.4
readable-stream: 3.6.2
dev: false
/tar-stream@3.1.6:
resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==}
dependencies:
b4a: 1.6.4
fast-fifo: 1.3.2
streamx: 2.15.4
dev: false
/text-table@0.2.0: /text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
dev: true dev: true
@ -3114,6 +3343,12 @@ packages:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
dev: true dev: true
/tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
dependencies:
safe-buffer: 5.2.1
dev: false
/type-check@0.4.0: /type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -3277,7 +3512,6 @@ packages:
/wrappy@1.0.2: /wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true
/xhr@2.6.0: /xhr@2.6.0:
resolution: {integrity: sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==} resolution: {integrity: sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==}
@ -3324,7 +3558,6 @@ packages:
/yallist@4.0.0: /yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
dev: true
/yaml@1.10.2: /yaml@1.10.2:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}

View File

@ -1,20 +1,56 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte';
export let src: string; export let src: string;
export let alt = ''; export let alt = '';
const u = new URL(src); const u = new URL(src);
const filename = u.pathname.split('/').pop() || '9a8sda'; const filename = u.pathname.split('/').pop() || '9a8sda';
const int = (parseInt(filename?.slice(0, 8), 16) % 7) + 1; const int = (parseInt(filename?.slice(0, 8), 16) % 7) + 1;
const frame = `/frames/frame_0${int}`;
let handlingError = false;
function handleError() {
if (handlingError) return;
handlingError = true;
console.log('error', { src });
const oldSrc = src;
src = '';
setTimeout(() => {
src = oldSrc;
handlingError = false;
}, 1000);
}
</script> </script>
<div class="frame" style="--frame: url(/frames/frame_0{int}.png)"> <div class="frame" style="--frame: url(/frames/frame_0{int}.png)">
<img src="/hang.png" class="hang" /> <picture class="painting-frame">
<picture> <!-- Load AVIF format -->
<source srcset="{frame}.avif" type="image/avif" />
<!-- If AVIF is not supported, load WebP format -->
<source srcset="{frame}.webp" type="image/webp" />
<!-- If neither AVIF nor WebP are supported, load PNG format -->
<img src="{frame}.png" alt="Painting Frame" />
</picture>
<img src="/hang.png" class="hang" alt="painting hanging cord" />
<picture class="person">
<source srcset={src.replace('.png', '.avif')} type="image/avif" />
<source srcset={src.replace('.png', '.webp')} type="image/webp" />
<source srcset={src.replace('.png', '.jpg')} type="image/jpeg" /> <source srcset={src.replace('.png', '.jpg')} type="image/jpeg" />
<img {src} {alt} /> <img {src} {alt} on:error={() => handleError()} />
</picture> </picture>
</div> </div>
<style> <style>
.painting-frame > * {
position: absolute;
width: 100%;
height: 100%;
object-fit: contain;
filter: drop-shadow(0px 0px 10px black) brightness(0.6) contrast(1.1);
}
.hang { .hang {
position: absolute; position: absolute;
top: 0px; top: 0px;
@ -34,13 +70,13 @@
position: absolute; position: absolute;
height: 100%; height: 100%;
width: 100%; width: 100%;
background: var(--frame); /* background: var(--frame); */
background-position: center center; background-position: center center;
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
filter: drop-shadow(0px 0px 20px black); filter: drop-shadow(0px 0px 20px black);
} }
img { .person img {
z-index: -1; z-index: -1;
max-width: 70%; max-width: 70%;
max-height: 70%; max-height: 70%;

View File

@ -22,6 +22,7 @@
portraitHairType?: 'straight' | 'curly' | 'bald'; portraitHairType?: 'straight' | 'curly' | 'bald';
portraitHairColor?: 'red' | 'brown' | 'blonde' | 'black' | 'grey' | 'white'; portraitHairColor?: 'red' | 'brown' | 'blonde' | 'black' | 'grey' | 'white';
portraitHairLength?: 'short' | 'medium' | 'long'; portraitHairLength?: 'short' | 'medium' | 'long';
portraitSkinColor?: 'very light' | 'light' | 'medium' | 'dark' | 'very dark';
portraitAccepted?: boolean; portraitAccepted?: boolean;
portraitPublic?: boolean; portraitPublic?: boolean;
@ -62,7 +63,8 @@
body: JSON.stringify({ body: JSON.stringify({
hairType: $data.portraitHairType, hairType: $data.portraitHairType,
hairColor: $data.portraitHairColor, hairColor: $data.portraitHairColor,
hairLength: $data.portraitHairLength hairLength: $data.portraitHairLength,
skinColor: $data.portraitSkinColor
}) })
}); });
@ -224,6 +226,14 @@
{#if $data.providePortrait && !loadingPortrait && !$data.portraitUrl} {#if $data.providePortrait && !loadingPortrait && !$data.portraitUrl}
<p> <p>
Wir werden {$data.adelsTitel || $data.name} mit Wir werden {$data.adelsTitel || $data.name} mit
<select placeholder="Typ" bind:value={$data.portraitSkinColor}>
<option value="very light">sehr heller</option>
<option value="light">heller</option>
<option value="medium">medium</option>
<option value="dark">dunkler</option>
<option value="very dark">sehr dunkler</option>
</select>
Haut und
<select placeholder="Typ" bind:value={$data.portraitHairType}> <select placeholder="Typ" bind:value={$data.portraitHairType}>
<option value="straight">glatten</option> <option value="straight">glatten</option>
<option value="curly">lockigen</option> <option value="curly">lockigen</option>
@ -297,21 +307,17 @@
margin-bottom: 200px; margin-bottom: 200px;
} }
.portrait-frame {
margin-top: 100px;
}
.portrait-frame.loaded { .portrait-frame.loaded {
margin-top: 100px;
border: none; border: none;
} }
button { button {
border: none; border: none;
border-radius: 5px; border-radius: 2px;
padding: 5px 9px; padding: 5px 9px;
margin-right: 10px; margin-right: 10px;
background: #866831; background: #866831;
box-shadow: 3px 3px 8px #e1b45f inset;
cursor: pointer; cursor: pointer;
} }

View File

@ -9,7 +9,7 @@ export async function getPublicPortraits() {
})).items })).items
} }
export function createPerson({ name, confidence, portrait, portrait_public, noble_name, hair_color, hair_type, hair_length }: { name: string, portrait: string, portrait_public: boolean, hair_type: string, hair_length: string, hair_color: string, confidence: number, noble_name: string }) { export function createPerson({ name, confidence, portrait, portrait_public, noble_name, hair_color, hair_type, hair_length, skin_color }: { name: string, portrait: string, portrait_public: boolean, hair_type: string, hair_length: string, hair_color: string, confidence: number, noble_name: string, skin_color: string }) {
return pb.collection("invites").create({ return pb.collection("invites").create({
name, name,
confidence, confidence,
@ -18,6 +18,7 @@ export function createPerson({ name, confidence, portrait, portrait_public, nobl
noble_name, noble_name,
hair_type, hair_type,
hair_length, hair_length,
hair_color hair_color,
skin_color
}) })
} }

View File

@ -1,8 +1,43 @@
import { json } from "@sveltejs/kit"; import { json } from "@sveltejs/kit";
import Jimp from "jimp";
import type { RequestHandler } from "./$types"; import type { RequestHandler } from "./$types";
import { putObject } from "$lib/helpers/minio"; import { putObject } from "$lib/helpers/minio";
import { generateImage } from "$lib/helpers/stability"; import { generateImage } from "$lib/helpers/stability";
import sharp from "sharp";
async function compressImage(imageName: string, imageBuffer: Buffer) {
const ja = performance.now()
const jpgBuffer = await sharp(imageBuffer)
.jpeg({ quality: 70 })
.withMetadata()
.toBuffer();
await putObject(imageName.replace(".png", ".jpg"), jpgBuffer, { "Content-Type": "image/jpeg" });
const jb = performance.now() - ja;
console.log(`[AI] JPG compression took ${jb}ms`)
const wa = performance.now()
const webpBuffer = await sharp(imageBuffer)
.webp({ quality: 70 })
.withMetadata()
.toBuffer()
await putObject(imageName.replace(".png", ".webp"), webpBuffer, { "Content-Type": "image/webp" });
const wb = performance.now() - wa;
console.log(`[AI] WebP compression took ${wb}ms`)
const aa = performance.now()
const aviBuffer = await sharp(imageBuffer)
.avif({ quality: 70 })
.withMetadata()
.toBuffer()
await putObject(imageName.replace(".png", ".avif"), aviBuffer, { "Content-Type": "image/avif" });
const ab = performance.now() - aa;
console.log(`[AI] AVIF compression took ${ab}ms`)
}
export const POST: RequestHandler = async ({ params, request }) => { export const POST: RequestHandler = async ({ params, request }) => {
@ -14,33 +49,39 @@ export const POST: RequestHandler = async ({ params, request }) => {
throw new Error("Name too long"); throw new Error("Name too long");
} }
const { hairType, hairColor, hairLength } = await request.json(); const { hairType, hairColor, hairLength, skinColor } = await request.json();
console.log(hairType, hairColor, hairLength) console.log(`[AI] Generating image for ${inputName} ${JSON.stringify({ hairType, hairColor, hairLength, skinColor })}`)
if (!hairType || !hairColor || !hairLength) { if (!hairType || !hairColor || !hairLength) {
throw new Error("Missing hairType, hairColor or hairLength"); throw new Error("Missing hairType, hairColor or hairLength");
} }
const prompt = `realistic portrait oil painting of a masked ${inputName}, baroque, in the style of Charles Vess, masked ball attire, opulence, mystery, elegance, ${hairLength} ${hairType} ${hairColor} hair, darker skin`; const prompt = `realistic portrait oil painting of a masked ${inputName}, baroque, in the style of Charles Vess, masked ball attire, opulence, mystery, elegance, ${hairLength} ${hairType} ${hairColor} hair, ${skinColor} skin`;
const negativePrompt = "blurry, multiple persons, picture frame" const negativePrompt = "blurry, multiple persons, picture frame"
const a = performance.now() const a = performance.now()
// #const image = await openai.image(prompt);
const image = await generateImage(prompt, negativePrompt); const image = await generateImage(prompt, negativePrompt);
const duration = performance.now() - a; const d = performance.now() - a;
console.log(`[AI] Image generation took ${d}ms`)
const imageName = `${Math.random().toString(16).substring(3, 10)}-${inputName.toLowerCase().split(" ").slice(0, 5).join("-").slice(0, 25)}.png` const imageName = `${Math.random().toString(16).substring(3, 10)}-${inputName.toLowerCase().split(" ").slice(0, 5).join("-").slice(0, 25)}.png`
await putObject(imageName, Buffer.from(image.base64, 'base64'), { "Content-Type": "image/png" }); const imageBuffer = Buffer.from(image.base64, 'base64');
const img = await Jimp.read(Buffer.from(image.base64, "base64")); const a2 = performance.now()
const jpgBuffer = await img.quality(70).getBufferAsync(Jimp.MIME_JPEG); const pngBuffer = await sharp(imageBuffer)
.png({ compressionLevel: 9, adaptiveFiltering: true, force: true })
.withMetadata()
.toBuffer()
await putObject(imageName.replace(".png", ".jpg"), jpgBuffer, { "Content-Type": "image/jpeg" }); await putObject(imageName, pngBuffer, { "Content-Type": "image/png" });
const d2 = performance.now() - a2;
console.log(`[AI] PNG compression took ${d2}ms`)
await compressImage(imageName, imageBuffer)
return json({ return json({
duration, url: `https://s3.max-richter.dev/silvester23/${imageName}`
url: `https://s3-api.app.max-richter.dev/silvester23/${imageName}`
}) })
} }

View File

@ -26,6 +26,7 @@ export const POST: RequestHandler = async ({ request }) => {
hair_type: body.portraitHairType, hair_type: body.portraitHairType,
hair_color: body.portraitHairColor, hair_color: body.portraitHairColor,
portrait_public: body.portraitPublic, portrait_public: body.portraitPublic,
skin_color: body.portraitSkinColor,
}); });
} catch (e) { } catch (e) {
console.log(e) console.log(e)

View File

@ -93,6 +93,7 @@
:global(html) { :global(html) {
background-image: url(/pattern.jpg) !important; background-image: url(/pattern.jpg) !important;
backdrop-filter: brightness(0.5) !important; backdrop-filter: brightness(0.5) !important;
background-size: 25%;
} }
.wrapper { .wrapper {
max-width: 1300px; max-width: 1300px;

View File

@ -1,7 +0,0 @@
<script lang="ts">
import ImageFrame from '$lib/components/ImageFrame.svelte';
</script>
<ImageFrame
src="https://s3-api.app.max-richter.dev/silvester23/13770f0-earl-maximus-of-richterla.png"
/>

BIN
static/frames/frame_01.avif Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 427 KiB

BIN
static/frames/frame_01.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
static/frames/frame_02.avif Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 669 KiB

After

Width:  |  Height:  |  Size: 613 KiB

BIN
static/frames/frame_02.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
static/frames/frame_03.avif Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 417 KiB

BIN
static/frames/frame_03.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
static/frames/frame_04.avif Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 608 KiB

BIN
static/frames/frame_04.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
static/frames/frame_05.avif Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 MiB

After

Width:  |  Height:  |  Size: 479 KiB

BIN
static/frames/frame_05.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
static/frames/frame_06.avif Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 397 KiB

BIN
static/frames/frame_06.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
static/frames/frame_07.avif Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 424 KiB

BIN
static/frames/frame_07.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
static/frames/frame_08.avif Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 453 KiB

After

Width:  |  Height:  |  Size: 328 KiB

BIN
static/frames/frame_08.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB