[+] Thumb
This commit is contained in:
+41
-22
@@ -1,6 +1,7 @@
|
||||
import { Elysia, redirect, t } from "elysia"
|
||||
import ExifReader from "exifreader"
|
||||
import { exists, mkdir } from "fs/promises"
|
||||
import sharp from "sharp"
|
||||
|
||||
// --- Configuration ---
|
||||
const FOLDER_BASE = "./data"
|
||||
@@ -13,8 +14,9 @@ interface PhotoMetadata {
|
||||
id: string
|
||||
owner_key: string // This will be filtered out for public requests
|
||||
upload_time: string
|
||||
original_photo: { filename: string, path: string, exif: any }
|
||||
edited_photo: { filename: string, path: string, exif: any }
|
||||
original_photo: string
|
||||
edited_photo: string
|
||||
thumbnail: string
|
||||
[key: string]: any // Custom properties
|
||||
}
|
||||
|
||||
@@ -52,10 +54,30 @@ async function saveMetadata(data: PhotoMetadata[]): Promise<void> {
|
||||
* Extracts EXIF data from an uploaded file.
|
||||
*/
|
||||
async function getExifData(file: File): Promise<any> {
|
||||
return ExifReader.load(await file.arrayBuffer(), {
|
||||
expanded: true,
|
||||
// const whitelist = ["Image Height", "Image Width", "Subsampling", "DateTime", "ExposureProgram",
|
||||
// "ISOSpeedRatings", "SensitivityType", "RecommendedExposureIndex",
|
||||
// "DateTimeOriginal", "OffsetTime", "BrightnessValue", "MeteringMode", "ExposureMode", "ColorSpace",
|
||||
// "FNumber", "ExposureTime", "FocalLength", "FocalLengthIn35mmFilm", "ScaleFactorTo35mmEquivalent", "FieldOfView", "ApertureValue", "ExposureBiasValue", "Flash", "WhiteBalance", "FocalLengthIn35mmFormat",
|
||||
// "LensModel", "LensSpecification"]
|
||||
const rawRaw = ExifReader.load(await file.arrayBuffer(), {
|
||||
expanded: false,
|
||||
includeUnknown: false,
|
||||
})
|
||||
const raw = JSON.parse(JSON.stringify(rawRaw))
|
||||
|
||||
const data = Object.entries(raw)
|
||||
// .filter(([key, _]) => whitelist.includes(key))
|
||||
.reduce((acc, [key, value]) => {
|
||||
acc[key] = (value as any)?.description as string
|
||||
return acc
|
||||
}, {} as Record<string, string>)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
async function generateThumbnail(originalPath: string, thumbnailPath: string) {
|
||||
const bytes = await Bun.file(originalPath).arrayBuffer()
|
||||
await sharp(bytes).resize({ width: 1000 }).webp({ quality: 80 }).toFile(thumbnailPath)
|
||||
}
|
||||
|
||||
// --- Elysia Server ---
|
||||
@@ -83,7 +105,7 @@ export const app = new Elysia()
|
||||
})
|
||||
.get("/", ({ redirect }) => redirect("https://aza.moe/photo"))
|
||||
|
||||
.post("/upload", async ({ body, headers, status }) => {
|
||||
.post("/upload", async ({ body, headers }) => {
|
||||
const { owner_key, photo, edited_photo } = body
|
||||
checkHeaderKey(headers)
|
||||
|
||||
@@ -99,31 +121,28 @@ export const app = new Elysia()
|
||||
const editedExt = edited_photo.name.split(".").pop() || "jpg"
|
||||
|
||||
// Using "1" for original and "2" for edited
|
||||
const originalPath = `${FOLDER_PHOTOS}/${id}-1.${originalExt}`
|
||||
const editedPath = `${FOLDER_PHOTOS}/${id}-2.${editedExt}`
|
||||
const originalPath = `${FOLDER_PHOTOS}/${id}/orig.${originalExt}`
|
||||
const editedPath = `${FOLDER_PHOTOS}/${id}/edit.${editedExt}`
|
||||
|
||||
// Run file saving and EXIF parsing in parallel
|
||||
const [originalExif, editedExif] = await Promise.all([
|
||||
const [originalExif] = await Promise.all([
|
||||
getExifData(photo),
|
||||
getExifData(edited_photo),
|
||||
Bun.write(originalPath, photo),
|
||||
Bun.write(editedPath, edited_photo),
|
||||
])
|
||||
|
||||
// Generate thumbnail
|
||||
const thumbnailPath = `${FOLDER_PHOTOS}/${id}/thumb.webp`
|
||||
await generateThumbnail(originalPath, thumbnailPath)
|
||||
|
||||
// 4. Create new metadata entry
|
||||
const newEntry: PhotoMetadata = {
|
||||
id, owner_key,
|
||||
upload_time: new Date().toISOString(),
|
||||
original_photo: {
|
||||
filename: photo.name,
|
||||
path: originalPath,
|
||||
exif: originalExif,
|
||||
},
|
||||
edited_photo: {
|
||||
filename: edited_photo.name,
|
||||
path: editedPath,
|
||||
exif: editedExif,
|
||||
},
|
||||
exif: originalExif,
|
||||
original_photo: Bun.file(originalPath).name!,
|
||||
edited_photo: Bun.file(editedPath).name!,
|
||||
thumbnail: Bun.file(thumbnailPath).name!,
|
||||
}
|
||||
|
||||
// 5. Save updated metadata
|
||||
@@ -173,9 +192,9 @@ export const app = new Elysia()
|
||||
// Not found or hidden
|
||||
if (!photo || photo.hide === true) done(404, "Photo not found")
|
||||
|
||||
const file = Bun.file(photo.edited_photo.path)
|
||||
const file = Bun.file(photo.edited_photo)
|
||||
if (!(await file.exists())) {
|
||||
console.error(`Missing file for ID ${id} at ${photo.edited_photo.path}`)
|
||||
console.error(`Missing file for ID ${id} at ${photo.edited_photo}`)
|
||||
done(404, "Photo file not found on disk")
|
||||
}
|
||||
return file
|
||||
@@ -185,7 +204,7 @@ export const app = new Elysia()
|
||||
|
||||
// ----- 4. POST /edit -----
|
||||
// Edits a specific field in the metadata.
|
||||
.post("/edit", async ({ headers, body, status }) => {
|
||||
.post("/edit", async ({ headers, body }) => {
|
||||
const { id, key, field, value } = body
|
||||
const metadata = await getMetadata()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user