2021-04-21 19:22:58 +02:00
|
|
|
import sizeOf from 'image-size'
|
|
|
|
|
import fs from 'fs'
|
|
|
|
|
import exif from 'exif'
|
|
|
|
|
import sharp from 'sharp'
|
2021-05-17 00:27:47 +02:00
|
|
|
|
2021-05-17 01:12:39 +02:00
|
|
|
import {UPLOAD_DIR, UPLOAD_URL} from './Dirs'
|
2021-05-29 17:58:05 +02:00
|
|
|
import Db, { OrderBy, WhereRaw } from './Db'
|
2021-05-28 23:01:00 +02:00
|
|
|
import { Dim } from '../common/Geometry'
|
2021-05-29 17:58:05 +02:00
|
|
|
import { logger } from '../common/Util'
|
2021-07-11 16:37:34 +02:00
|
|
|
import { Tag, ImageInfo } from '../common/Types'
|
2021-04-21 19:22:58 +02:00
|
|
|
|
2021-05-29 17:58:05 +02:00
|
|
|
const log = logger('Images.ts')
|
|
|
|
|
|
|
|
|
|
const resizeImage = async (filename: string): Promise<void> => {
|
2021-04-21 19:22:58 +02:00
|
|
|
if (!filename.toLowerCase().match(/\.(jpe?g|webp|png)$/)) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const imagePath = `${UPLOAD_DIR}/${filename}`
|
|
|
|
|
const imageOutPath = `${UPLOAD_DIR}/r/${filename}`
|
|
|
|
|
const orientation = await getExifOrientation(imagePath)
|
|
|
|
|
|
|
|
|
|
let sharpImg = sharp(imagePath, { failOnError: false })
|
|
|
|
|
// when image is rotated to the left or right, switch width/height
|
|
|
|
|
// https://jdhao.github.io/2019/07/31/image_rotation_exif_info/
|
|
|
|
|
if (orientation === 6) {
|
|
|
|
|
sharpImg = sharpImg.rotate()
|
|
|
|
|
} else if (orientation === 3) {
|
|
|
|
|
sharpImg = sharpImg.rotate().rotate()
|
|
|
|
|
} else if (orientation === 8) {
|
|
|
|
|
sharpImg = sharpImg.rotate().rotate().rotate()
|
|
|
|
|
}
|
|
|
|
|
const sizes = [
|
|
|
|
|
[150, 100],
|
|
|
|
|
[375, 210],
|
|
|
|
|
]
|
2021-05-29 17:58:05 +02:00
|
|
|
for (const [w,h] of sizes) {
|
|
|
|
|
log.info(w, h, imagePath)
|
|
|
|
|
await sharpImg
|
|
|
|
|
.resize(w, h, { fit: 'contain' })
|
|
|
|
|
.toFile(`${imageOutPath}-${w}x${h}.webp`)
|
2021-04-21 19:22:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-29 17:58:05 +02:00
|
|
|
async function getExifOrientation(imagePath: string): Promise<number> {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
new exif.ExifImage({ image: imagePath }, (error, exifData) => {
|
2021-04-21 19:22:58 +02:00
|
|
|
if (error) {
|
|
|
|
|
resolve(0)
|
|
|
|
|
} else {
|
2021-05-29 17:58:05 +02:00
|
|
|
resolve(exifData.image.Orientation || 0)
|
2021-04-21 19:22:58 +02:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-03 23:46:09 +02:00
|
|
|
const getAllTags = (db: Db): Tag[] => {
|
|
|
|
|
const query = `
|
|
|
|
|
select c.id, c.slug, c.title, count(*) as total from categories c
|
|
|
|
|
inner join image_x_category ixc on c.id = ixc.category_id
|
2021-06-03 23:49:03 +02:00
|
|
|
inner join images i on i.id = ixc.image_id
|
2021-06-03 23:46:09 +02:00
|
|
|
group by c.id order by total desc;`
|
|
|
|
|
return db._getMany(query).map(row => ({
|
|
|
|
|
id: parseInt(row.id, 10) || 0,
|
|
|
|
|
slug: row.slug,
|
|
|
|
|
title: row.title,
|
|
|
|
|
total: parseInt(row.total, 10) || 0,
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-29 17:58:05 +02:00
|
|
|
const getTags = (db: Db, imageId: number): Tag[] => {
|
2021-05-22 01:51:44 +02:00
|
|
|
const query = `
|
|
|
|
|
select * from categories c
|
|
|
|
|
inner join image_x_category ixc on c.id = ixc.category_id
|
|
|
|
|
where ixc.image_id = ?`
|
2021-05-29 17:58:05 +02:00
|
|
|
return db._getMany(query, [imageId]).map(row => ({
|
2021-06-03 23:46:09 +02:00
|
|
|
id: parseInt(row.id, 10) || 0,
|
2021-05-29 17:58:05 +02:00
|
|
|
slug: row.slug,
|
2021-06-02 23:35:01 +02:00
|
|
|
title: row.title,
|
2021-06-03 23:46:09 +02:00
|
|
|
total: 0,
|
2021-05-29 17:58:05 +02:00
|
|
|
}))
|
2021-05-22 01:51:44 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-29 17:58:05 +02:00
|
|
|
const imageFromDb = (db: Db, imageId: number): ImageInfo => {
|
2021-05-22 01:51:44 +02:00
|
|
|
const i = db.get('images', { id: imageId })
|
|
|
|
|
return {
|
|
|
|
|
id: i.id,
|
2021-07-11 18:44:59 +02:00
|
|
|
uploaderUserId: i.uploader_user_id,
|
2021-05-22 01:51:44 +02:00
|
|
|
filename: i.filename,
|
|
|
|
|
url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`,
|
|
|
|
|
title: i.title,
|
2021-05-29 17:58:05 +02:00
|
|
|
tags: getTags(db, i.id),
|
2021-05-22 01:51:44 +02:00
|
|
|
created: i.created * 1000,
|
2021-06-20 14:21:48 +02:00
|
|
|
width: i.width,
|
|
|
|
|
height: i.height,
|
2021-05-22 01:51:44 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-29 17:58:05 +02:00
|
|
|
const allImagesFromDb = (
|
|
|
|
|
db: Db,
|
|
|
|
|
tagSlugs: string[],
|
|
|
|
|
orderBy: string
|
|
|
|
|
): ImageInfo[] => {
|
|
|
|
|
const orderByMap = {
|
2021-05-22 01:51:44 +02:00
|
|
|
alpha_asc: [{filename: 1}],
|
|
|
|
|
alpha_desc: [{filename: -1}],
|
|
|
|
|
date_asc: [{created: 1}],
|
|
|
|
|
date_desc: [{created: -1}],
|
2021-05-29 17:58:05 +02:00
|
|
|
} as Record<string, OrderBy>
|
2021-05-22 01:51:44 +02:00
|
|
|
|
|
|
|
|
// TODO: .... clean up
|
2021-05-29 17:58:05 +02:00
|
|
|
const wheresRaw: WhereRaw = {}
|
2021-05-22 15:48:13 +02:00
|
|
|
if (tagSlugs.length > 0) {
|
|
|
|
|
const c = db.getMany('categories', {slug: {'$in': tagSlugs}})
|
2021-05-22 01:51:44 +02:00
|
|
|
if (!c) {
|
|
|
|
|
return []
|
|
|
|
|
}
|
2021-05-22 15:48:13 +02:00
|
|
|
const where = db._buildWhere({
|
|
|
|
|
'category_id': {'$in': c.map(x => x.id)}
|
|
|
|
|
})
|
2021-05-22 01:51:44 +02:00
|
|
|
const ids = db._getMany(`
|
|
|
|
|
select i.id from image_x_category ixc
|
2021-05-22 15:48:13 +02:00
|
|
|
inner join images i on i.id = ixc.image_id ${where.sql};
|
|
|
|
|
`, where.values).map(img => img.id)
|
2021-05-22 01:51:44 +02:00
|
|
|
if (ids.length === 0) {
|
|
|
|
|
return []
|
|
|
|
|
}
|
|
|
|
|
wheresRaw['id'] = {'$in': ids}
|
|
|
|
|
}
|
2021-05-29 17:58:05 +02:00
|
|
|
const images = db.getMany('images', wheresRaw, orderByMap[orderBy])
|
2021-05-22 01:51:44 +02:00
|
|
|
|
|
|
|
|
return images.map(i => ({
|
|
|
|
|
id: i.id as number,
|
2021-07-11 18:44:59 +02:00
|
|
|
uploaderUserId: i.uploader_user_id,
|
2021-05-22 01:51:44 +02:00
|
|
|
filename: i.filename,
|
|
|
|
|
url: `${UPLOAD_URL}/${encodeURIComponent(i.filename)}`,
|
|
|
|
|
title: i.title,
|
2021-05-29 17:58:05 +02:00
|
|
|
tags: getTags(db, i.id),
|
2021-05-22 01:51:44 +02:00
|
|
|
created: i.created * 1000,
|
2021-06-20 14:21:48 +02:00
|
|
|
width: i.width,
|
|
|
|
|
height: i.height,
|
2021-05-22 01:51:44 +02:00
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-20 14:21:48 +02:00
|
|
|
/**
|
|
|
|
|
* @deprecated old function, now database is used
|
|
|
|
|
*/
|
2021-05-29 17:58:05 +02:00
|
|
|
const allImagesFromDisk = (
|
|
|
|
|
tags: string[],
|
|
|
|
|
sort: string
|
|
|
|
|
): ImageInfo[] => {
|
2021-05-21 00:43:02 +02:00
|
|
|
let images = fs.readdirSync(UPLOAD_DIR)
|
2021-04-21 19:22:58 +02:00
|
|
|
.filter(f => f.toLowerCase().match(/\.(jpe?g|webp|png)$/))
|
|
|
|
|
.map(f => ({
|
2021-05-22 01:51:44 +02:00
|
|
|
id: 0,
|
2021-07-11 18:44:59 +02:00
|
|
|
uploaderUserId: null,
|
2021-04-21 19:22:58 +02:00
|
|
|
filename: f,
|
2021-05-04 09:48:14 +02:00
|
|
|
url: `${UPLOAD_URL}/${encodeURIComponent(f)}`,
|
2021-05-22 01:51:44 +02:00
|
|
|
title: f.replace(/\.[a-z]+$/, ''),
|
2021-05-29 17:58:05 +02:00
|
|
|
tags: [] as Tag[],
|
2021-05-22 01:51:44 +02:00
|
|
|
created: fs.statSync(`${UPLOAD_DIR}/${f}`).mtime.getTime(),
|
2021-06-20 14:21:48 +02:00
|
|
|
width: 0, // may have to fill when the function is used again
|
|
|
|
|
height: 0, // may have to fill when the function is used again
|
2021-04-21 19:22:58 +02:00
|
|
|
}))
|
2021-05-21 00:43:02 +02:00
|
|
|
|
|
|
|
|
switch (sort) {
|
|
|
|
|
case 'alpha_asc':
|
|
|
|
|
images = images.sort((a, b) => {
|
2021-07-11 16:37:34 +02:00
|
|
|
return a.filename > b.filename ? 1 : -1
|
2021-05-21 00:43:02 +02:00
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'alpha_desc':
|
|
|
|
|
images = images.sort((a, b) => {
|
2021-07-11 16:37:34 +02:00
|
|
|
return a.filename < b.filename ? 1 : -1
|
2021-05-21 00:43:02 +02:00
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'date_asc':
|
|
|
|
|
images = images.sort((a, b) => {
|
2021-05-22 01:51:44 +02:00
|
|
|
return a.created > b.created ? 1 : -1
|
2021-05-21 00:43:02 +02:00
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 'date_desc':
|
|
|
|
|
default:
|
|
|
|
|
images = images.sort((a, b) => {
|
2021-05-22 01:51:44 +02:00
|
|
|
return a.created < b.created ? 1 : -1
|
2021-05-21 00:43:02 +02:00
|
|
|
})
|
|
|
|
|
break;
|
|
|
|
|
}
|
2021-04-21 19:22:58 +02:00
|
|
|
return images
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-28 23:01:00 +02:00
|
|
|
async function getDimensions(imagePath: string): Promise<Dim> {
|
2021-05-29 17:58:05 +02:00
|
|
|
const dimensions = sizeOf(imagePath)
|
2021-04-21 19:22:58 +02:00
|
|
|
const orientation = await getExifOrientation(imagePath)
|
|
|
|
|
// when image is rotated to the left or right, switch width/height
|
|
|
|
|
// https://jdhao.github.io/2019/07/31/image_rotation_exif_info/
|
|
|
|
|
if (orientation === 6 || orientation === 8) {
|
|
|
|
|
return {
|
2021-05-28 23:01:00 +02:00
|
|
|
w: dimensions.height || 0,
|
|
|
|
|
h: dimensions.width || 0,
|
2021-04-21 19:22:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
2021-05-28 23:01:00 +02:00
|
|
|
return {
|
|
|
|
|
w: dimensions.width || 0,
|
|
|
|
|
h: dimensions.height || 0,
|
|
|
|
|
}
|
2021-04-21 19:22:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default {
|
2021-05-22 01:51:44 +02:00
|
|
|
allImagesFromDisk,
|
|
|
|
|
imageFromDb,
|
|
|
|
|
allImagesFromDb,
|
2021-06-03 23:46:09 +02:00
|
|
|
getAllTags,
|
2021-04-21 19:22:58 +02:00
|
|
|
resizeImage,
|
|
|
|
|
getDimensions,
|
|
|
|
|
}
|