Migrate assets to webp and fix coin units
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -37,6 +37,7 @@ next-env.d.ts
|
||||
|
||||
# assets
|
||||
public/photos
|
||||
public/photos-original
|
||||
public/done
|
||||
tmp
|
||||
|
||||
|
||||
12
bin/build-textures.ts
Normal file
12
bin/build-textures.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { getTypes } from '@/api/types/get-types';
|
||||
import { generateImage } from '@/core/utils/merge-textures';
|
||||
|
||||
const coins = (await getTypes({ category: 'coins' })).filter(coin => coin.shape !== 'Round');
|
||||
|
||||
for (const [coinIndex, coin] of coins.entries()) {
|
||||
console.log('coin', coinIndex, 'of', coins.length);
|
||||
await generateImage(coin.id, 'base');
|
||||
await generateImage(coin.id, 'bump');
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
@@ -4,10 +4,11 @@ import { NumistaType } from '@/types/numista';
|
||||
import { asyncSpawn } from './async-spawn';
|
||||
import puppeteer, { Page } from 'puppeteer';
|
||||
import { writeFile } from 'fs/promises';
|
||||
import { getTextureUrl } from '@/core/utils/texture-urls';
|
||||
|
||||
const baseDir = './public';
|
||||
|
||||
export const downloadFile = async (page: Page, url: string) => {
|
||||
export const downloadFile = async (page: Page, url: string): Promise<[string, string, string?]> => {
|
||||
const filepath = url.split('catalogue')[1];
|
||||
const folder = path.join(baseDir, filepath.split('/').slice(0, -1).join('/'));
|
||||
console.log(url);
|
||||
@@ -20,7 +21,18 @@ export const downloadFile = async (page: Page, url: string) => {
|
||||
await writeFile(destination, await pageRes.buffer());
|
||||
}
|
||||
|
||||
return destination;
|
||||
const destinationWebp = destination.replace(/\.\w*$/g, '.webp')
|
||||
|
||||
await asyncSpawn('cwebp', [destination, '-o', destinationWebp])
|
||||
|
||||
let color: string | undefined;
|
||||
const palette = await Vibrant.from(destination).getPalette();
|
||||
|
||||
if (palette.Vibrant) {
|
||||
color = 'rgb(' + palette.Vibrant.rgb.join(',') + ')';
|
||||
}
|
||||
|
||||
return [destinationWebp, destination, color];
|
||||
};
|
||||
|
||||
export const fetchTextures = async (type: NumistaType) => {
|
||||
@@ -31,33 +43,40 @@ export const fetchTextures = async (type: NumistaType) => {
|
||||
page.setViewport({ width: 1280, height: 926 });
|
||||
|
||||
if (type?.obverse?.picture) {
|
||||
const destination = await downloadFile(page, type.obverse.picture);
|
||||
type.obverse.picture = destination.split('public')[1];
|
||||
texturesToBump.push(type.obverse.picture);
|
||||
const palette = await Vibrant.from(destination).getPalette();
|
||||
const [destinationWebp, destination, color] = await downloadFile(page, type.obverse.picture);
|
||||
type.obverse.picture = destinationWebp.split('public')[1];
|
||||
texturesToBump.push(`./public${destination.split('public')[1]}`);
|
||||
|
||||
if (palette.Vibrant) {
|
||||
type.color = 'rgb(' + palette.Vibrant.rgb.join(',') + ')';
|
||||
if (color) {
|
||||
type.color = color;
|
||||
}
|
||||
}
|
||||
|
||||
if (type?.reverse?.picture) {
|
||||
const destination = await downloadFile(page, type.reverse.picture);
|
||||
type.reverse.picture = destination.split('public')[1];
|
||||
texturesToBump.push(type.reverse.picture);
|
||||
const [destinationWebp, destination] = await downloadFile(page, type.reverse.picture);
|
||||
type.reverse.picture = destinationWebp.split('public')[1];
|
||||
texturesToBump.push(`./public${destination.split('public')[1]}`);
|
||||
}
|
||||
|
||||
if (type?.edge?.picture) {
|
||||
const destination = await downloadFile(page, type.edge.picture);
|
||||
type.edge.picture = destination.split('public')[1];
|
||||
const [destinationWebp, destination] = await downloadFile(page, type.edge.picture);
|
||||
type.edge.picture = destinationWebp.split('public')[1];
|
||||
await asyncSpawn('rm', [`./public${destination.split('public')[1]}`])
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
|
||||
for (const texture in texturesToBump) {
|
||||
await asyncSpawn('./bin/generate-depth.sh', [`./public${texture}`]);
|
||||
const bumpPngs = []
|
||||
for (const texture of texturesToBump) {
|
||||
await asyncSpawn('./bin/generate-depth.sh', [texture]);
|
||||
const bumpTextureWebp = getTextureUrl(texture, 'bump')
|
||||
const bumpTexturePng = bumpTextureWebp.replace(/(\.[^/.]+)$/, '.png')
|
||||
bumpPngs.push(bumpTexturePng)
|
||||
await asyncSpawn('cwebp', [bumpTexturePng, '-o', bumpTextureWebp])
|
||||
}
|
||||
|
||||
await asyncSpawn('rm', [...texturesToBump,...bumpPngs])
|
||||
|
||||
return type;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@@ -31,7 +31,7 @@ cp "$file" ./tmp
|
||||
fi
|
||||
|
||||
cd marigold
|
||||
pip install -r requirements.txt 2>&1 | grep -v "Requirement already satisfied"
|
||||
pip install -r requirements.txt
|
||||
|
||||
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True python run.py \
|
||||
--input_rgb ../../tmp \
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NumistaType } from '@/types/numista';
|
||||
import { db } from './db';
|
||||
|
||||
const bannedKeys = ['issuing_entity']
|
||||
const bannedKeys = ['issuing_entity'];
|
||||
|
||||
const omit = (data: object, keys: string[] = []) =>
|
||||
Object.fromEntries(
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"prebuild": "bun build-textures",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"get-type": "bun ./bin/get-type.ts",
|
||||
"build-textures": "bun ./bin/build-textures.ts",
|
||||
"generate-depth": "./bin/generate-depth.sh"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
BIN
prisma/main.db
BIN
prisma/main.db
Binary file not shown.
2290
prisma/main.dump.sql
2290
prisma/main.dump.sql
File diff suppressed because one or more lines are too long
@@ -1,5 +1,6 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "debian-openssl-3.0.x"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
|
||||
144
src/core/utils/merge-textures.ts
Normal file
144
src/core/utils/merge-textures.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/* eslint-disable no-console */
|
||||
import { getTextureUrl } from '@/core/utils/texture-urls';
|
||||
import { getType } from '@/api/types/get-type';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { spawn } from 'child_process';
|
||||
import path from 'path';
|
||||
|
||||
const SIZE = 2048;
|
||||
|
||||
const asyncSpawn = (command: string, args: string[]) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
child.on('error', reject);
|
||||
child.stdout?.on('data', process.stdout.write);
|
||||
child.stderr?.on('data', process.stderr.write);
|
||||
child.on('close', code => {
|
||||
if (code === 0) {
|
||||
resolve(code);
|
||||
} else {
|
||||
reject(code);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getEdgeRepeatNumber = (shape?: string) => {
|
||||
switch (shape?.toLowerCase?.()) {
|
||||
case 'spanish flower':
|
||||
return 7;
|
||||
|
||||
default:
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
|
||||
const getPreviousResult = async (id: number, type: string) => {
|
||||
try {
|
||||
return await readFile(path.join('./public/photos/joined', `${id}-${type}.webp`));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
type CoinProps = Awaited<ReturnType<typeof getType>>;
|
||||
|
||||
export const generateImage = async (coinProps: CoinProps | number, type: 'base' | 'bump') => {
|
||||
const id = typeof coinProps === 'number' ? coinProps : coinProps?.id;
|
||||
|
||||
if (!id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousResult = await getPreviousResult(id, type);
|
||||
|
||||
if (previousResult) {
|
||||
return previousResult;
|
||||
}
|
||||
|
||||
const coin = typeof coinProps === 'number' ? await getType(id) : coinProps;
|
||||
const edgeRepeatNumber = getEdgeRepeatNumber(coin?.shape);
|
||||
|
||||
if (!coin) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notFounds = 0;
|
||||
|
||||
try {
|
||||
await asyncSpawn('magick', [
|
||||
path.join('./public', getTextureUrl(coin.obverse?.picture, type)),
|
||||
'-resize',
|
||||
`${SIZE}x${SIZE}!`,
|
||||
path.join('./public/photos/joined', `${id}-${type}-obverse.webp`)
|
||||
]);
|
||||
} catch {
|
||||
notFounds++;
|
||||
await asyncSpawn('magick', [
|
||||
'-size',
|
||||
`${SIZE}x${SIZE}!`,
|
||||
`xc:${coin.color}`,
|
||||
path.join('./public/photos/joined', `${id}-${type}-obverse.webp`)
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
await asyncSpawn('magick', [
|
||||
path.join('./public', getTextureUrl(coin.reverse?.picture, type)),
|
||||
'-resize',
|
||||
`${SIZE}x${SIZE}!`,
|
||||
...(coin.orientation === 'coin' ? ['-rotate', '180'] : []),
|
||||
path.join('./public/photos/joined', `${id}-${type}-reverse.webp`)
|
||||
]);
|
||||
} catch {
|
||||
notFounds++;
|
||||
await asyncSpawn('magick', [
|
||||
'-size',
|
||||
`${SIZE}x${SIZE}!`,
|
||||
`xc:${coin.color}`,
|
||||
path.join('./public/photos/joined', `${id}-${type}-reverse.webp`)
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
await asyncSpawn('magick', [
|
||||
path.join('./public', getTextureUrl(coin.edge?.picture, type)),
|
||||
'-resize',
|
||||
`${SIZE * 2}x${SIZE / edgeRepeatNumber}!`,
|
||||
path.join('./public/photos/joined', `${id}-${type}-edge.webp`)
|
||||
]);
|
||||
} catch {
|
||||
notFounds++;
|
||||
await asyncSpawn('magick', [
|
||||
'-size',
|
||||
`${SIZE * 2}x${SIZE / edgeRepeatNumber}!`,
|
||||
`xc:${coin.color}`,
|
||||
path.join('./public/photos/joined', `${id}-${type}-edge.webp`)
|
||||
]);
|
||||
}
|
||||
|
||||
if (notFounds >= 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
await asyncSpawn('magick', [
|
||||
path.join('./public/photos/joined', `${id}-${type}-obverse.webp`),
|
||||
path.join('./public/photos/joined', `${id}-${type}-reverse.webp`),
|
||||
'+append',
|
||||
path.join('./public/photos/joined', `${id}-${type}.webp`)
|
||||
]);
|
||||
|
||||
for (let index = 0; index < edgeRepeatNumber; index++) {
|
||||
await asyncSpawn('magick', [
|
||||
path.join('./public/photos/joined', `${id}-${type}.webp`),
|
||||
path.join('./public/photos/joined', `${id}-${type}-edge.webp`),
|
||||
'-append',
|
||||
path.join('./public/photos/joined', `${id}-${type}.webp`)
|
||||
]);
|
||||
}
|
||||
|
||||
return await readFile(path.join('./public/photos/joined', `${id}-${type}.webp`));
|
||||
};
|
||||
@@ -1,11 +1,11 @@
|
||||
export function getTextureUrl(url?: string, type?: 'base' | 'bump') {
|
||||
if(!url) {
|
||||
throw new Error()
|
||||
if (!url) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if(type === 'bump') {
|
||||
return url?.replace(/(\.[^/.]+)$/, '_bump.png')
|
||||
if (type === 'bump') {
|
||||
return url?.replace(/(\.[^/.]+)$/, '_bump.webp');
|
||||
}
|
||||
|
||||
return url
|
||||
return url;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ import '@/styles/globals.css';
|
||||
import '@/styles/theme.css';
|
||||
import 'primeicons/primeicons.css';
|
||||
import 'primereact/resources/primereact.min.css';
|
||||
import { AppProps } from 'next/app';
|
||||
import { PrimeReactProvider } from 'primereact/api';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import App, { AppContext, AppProps } from 'next/app';
|
||||
import Div100vh from 'react-div-100vh';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
@@ -25,16 +25,3 @@ export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
MyApp.getInitialProps = async (appContext: AppContext) => {
|
||||
const appProps = await App.getInitialProps(appContext);
|
||||
|
||||
if (appContext.ctx.res?.statusCode === 404) {
|
||||
appContext.ctx.res.writeHead(302, { Location: '/' });
|
||||
appContext.ctx.res.end();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return { ...appProps };
|
||||
};
|
||||
|
||||
@@ -1,142 +1,8 @@
|
||||
/* eslint-disable no-console */
|
||||
import { NextApiHandler } from 'next';
|
||||
import { getTextureUrl } from '@/core/utils/texture-urls';
|
||||
import { getType } from '@/api/types/get-type';
|
||||
import { readFile } from 'fs/promises';
|
||||
import { spawn } from 'child_process';
|
||||
import path from 'path';
|
||||
import { generateImage } from '@/core/utils/merge-textures';
|
||||
|
||||
const SIZE = 2048
|
||||
|
||||
const asyncSpawn = (command: string, args: string[]) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(command, args, {
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
child.on('error', reject);
|
||||
child.stdout?.on('data', process.stdout.write);
|
||||
child.stderr?.on('data', process.stderr.write);
|
||||
child.on('close', code => {
|
||||
if (code === 0) {
|
||||
resolve(code);
|
||||
} else {
|
||||
reject(code);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getEdgeRepeatNumber = (shape?: string) => {
|
||||
switch(shape?.toLowerCase?.()) {
|
||||
case 'spanish flower':
|
||||
return 7;
|
||||
|
||||
default:
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
const getPreviousResult = async (id: number, type: string) => {
|
||||
try {
|
||||
return await readFile(path.join('./public/photos/joined', `${id}-${type}.jpg`));
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const generateImage = async (id: number, type: 'base' | 'bump') => {
|
||||
const previousResult = await getPreviousResult(id, type);
|
||||
|
||||
if (previousResult) {
|
||||
return previousResult;
|
||||
}
|
||||
|
||||
const coin = await getType(id);
|
||||
const edgeRepeatNumber = getEdgeRepeatNumber(coin?.shape)
|
||||
|
||||
if (!coin) {
|
||||
return;
|
||||
}
|
||||
|
||||
let notFounds = 0;
|
||||
|
||||
try {
|
||||
await asyncSpawn('magick', [
|
||||
path.join('./public', getTextureUrl(coin.obverse?.picture, type)),
|
||||
'-resize',
|
||||
`${SIZE}x${SIZE}!`,
|
||||
path.join('./public/photos/joined', `${id}-${type}-obverse.jpg`)
|
||||
]);
|
||||
} catch {
|
||||
notFounds++;
|
||||
await asyncSpawn('magick', [
|
||||
'-size',
|
||||
`${SIZE}x${SIZE}!`,
|
||||
`xc:${coin.color}`,
|
||||
path.join('./public/photos/joined', `${id}-${type}-obverse.jpg`)
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
await asyncSpawn('magick', [
|
||||
path.join('./public', getTextureUrl(coin.reverse?.picture, type)),
|
||||
'-resize',
|
||||
`${SIZE}x${SIZE}!`,
|
||||
...(coin.orientation === 'coin' ? ['-rotate', '180'] : []),
|
||||
path.join('./public/photos/joined', `${id}-${type}-reverse.jpg`)
|
||||
]);
|
||||
} catch {
|
||||
notFounds++;
|
||||
await asyncSpawn('magick', [
|
||||
'-size',
|
||||
`${SIZE}x${SIZE}!`,
|
||||
`xc:${coin.color}`,
|
||||
path.join('./public/photos/joined', `${id}-${type}-reverse.jpg`)
|
||||
]);
|
||||
}
|
||||
|
||||
try {
|
||||
await asyncSpawn('magick', [
|
||||
path.join('./public', getTextureUrl(coin.edge?.picture, type)),
|
||||
'-resize',
|
||||
`${SIZE*2}x${SIZE/edgeRepeatNumber}!`,
|
||||
path.join('./public/photos/joined', `${id}-${type}-edge.jpg`)
|
||||
]);
|
||||
} catch {
|
||||
notFounds++;
|
||||
await asyncSpawn('magick', [
|
||||
'-size',
|
||||
`${SIZE*2}x${SIZE/edgeRepeatNumber}!`,
|
||||
`xc:${coin.color}`,
|
||||
path.join('./public/photos/joined', `${id}-${type}-edge.jpg`)
|
||||
]);
|
||||
}
|
||||
|
||||
if(notFounds >= 3) {
|
||||
return
|
||||
}
|
||||
|
||||
await asyncSpawn('magick', [
|
||||
path.join('./public/photos/joined', `${id}-${type}-obverse.jpg`),
|
||||
path.join('./public/photos/joined', `${id}-${type}-reverse.jpg`),
|
||||
'+append',
|
||||
path.join('./public/photos/joined', `${id}-${type}.jpg`)
|
||||
]);
|
||||
|
||||
for (let index = 0; index < edgeRepeatNumber; index++) {
|
||||
await asyncSpawn('magick', [
|
||||
path.join('./public/photos/joined', `${id}-${type}.jpg`),
|
||||
path.join('./public/photos/joined', `${id}-${type}-edge.jpg`),
|
||||
'-append',
|
||||
path.join('./public/photos/joined', `${id}-${type}.jpg`)
|
||||
]);
|
||||
}
|
||||
|
||||
return await readFile(path.join('./public/photos/joined', `${id}-${type}.jpg`));
|
||||
};
|
||||
|
||||
const handler: NextApiHandler = async ({ query: {id, type} }, res) => {
|
||||
const handler: NextApiHandler = async ({ query: { id, type } }, res) => {
|
||||
if (!Number(id)) {
|
||||
res.status(400).json({
|
||||
message: `Invalid id ${JSON.stringify(id)}`
|
||||
@@ -145,7 +11,7 @@ const handler: NextApiHandler = async ({ query: {id, type} }, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if(type && !(['bump', 'base'].includes(type as string))) {
|
||||
if (type && !['bump', 'base'].includes(type as string)) {
|
||||
res.status(400).json({
|
||||
message: `Invalid type ${JSON.stringify(type)}`
|
||||
});
|
||||
@@ -153,7 +19,7 @@ const handler: NextApiHandler = async ({ query: {id, type} }, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedType = (type ?? 'base') as 'base' | 'bump'
|
||||
const parsedType = (type ?? 'base') as 'base' | 'bump';
|
||||
|
||||
try {
|
||||
const image = await generateImage(Number(id), parsedType);
|
||||
@@ -166,7 +32,7 @@ const handler: NextApiHandler = async ({ query: {id, type} }, res) => {
|
||||
return;
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'image/jpg');
|
||||
res.setHeader('Content-Type', 'image/webp');
|
||||
res.status(200).send(image);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
Reference in New Issue
Block a user