feat: Add imagesDimensions field.

This commit is contained in:
Caspian 2023-11-22 22:47:27 +00:00
parent 6166754636
commit 3fc91b60b7
8 changed files with 184 additions and 1 deletions

View File

@ -83,6 +83,7 @@
"hast-util-to-html": "9.0.0",
"i18next": "23.7.6",
"i18next-resources-to-backend": "1.2.0",
"image-size": "^1.0.2",
"immer": "10.0.3",
"ioredis": "5.3.2",
"isomorphic-dompurify": "1.9.0",

View File

@ -164,6 +164,9 @@ dependencies:
i18next-resources-to-backend:
specifier: 1.2.0
version: 1.2.0
image-size:
specifier: ^1.0.2
version: 1.0.2
immer:
specifier: 10.0.3
version: 10.0.3
@ -7564,6 +7567,14 @@ packages:
engines: {node: '>= 4'}
dev: true
/image-size@1.0.2:
resolution: {integrity: sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==}
engines: {node: '>=14.0.0'}
hasBin: true
dependencies:
queue: 6.0.2
dev: false
/immer@10.0.3:
resolution: {integrity: sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==}
dev: false
@ -10385,6 +10396,12 @@ packages:
resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
dev: false
/queue@6.0.2:
resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==}
dependencies:
inherits: 2.0.4
dev: false
/quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
dev: false

View File

@ -0,0 +1 @@
ALTER TABLE "Metadata" ADD COLUMN "image_dimensions" JSONB;

View File

@ -18,6 +18,15 @@ model Metadata {
ai_summary_ja String?
ai_score Int?
ai_score_reason String?
// Expected JSON structure for the image_dimensions field:
// {
// "<uri>": {
// "width": 123,
// "height": 123
// },
// ...
// }
image_dimensions Json?
@@id([uri])
@@index([uri])
}

View File

@ -1,5 +1,6 @@
import { getQuery, NextServerResponse } from "~/lib/server-helper"
import { getFeed } from "~/models/home.model"
import { decoratePageForImageDimensions } from "~/queries/page.server"
export async function GET(req: Request) {
const query = getQuery(req)
@ -16,6 +17,11 @@ export async function GET(req: Request) {
useHTML: false,
topic: query.topic,
})
for (const item of result.list) {
await decoratePageForImageDimensions(item)
}
const res = new NextServerResponse()
return res.status(200).json(result)
}

View File

@ -1,6 +1,7 @@
import { getQuery, NextServerResponse } from "~/lib/server-helper"
import { PagesSortTypes, PageVisibilityEnum } from "~/lib/types"
import { getPagesBySite } from "~/models/page.model"
import { decoratePageForImageDimensions } from "~/queries/page.server"
export async function GET(req: Request) {
const query = getQuery(req)
@ -20,6 +21,11 @@ export async function GET(req: Request) {
: [query.tags as string],
}),
})
for (const item of result.list) {
await decoratePageForImageDimensions(item)
}
const res = new NextServerResponse()
return res.status(200).json(result)
}

View File

@ -89,6 +89,7 @@ export type ExpandedNote = NoteEntity & {
summary?: string
cover?: string
images?: string[]
imageDimensions?: Record<string, { width: number; height: number }>
frontMatter?: Record<string, any>
slug?: string
audio?: string

View File

@ -1,4 +1,7 @@
import https from "https"
import url from "url"
import AsyncLock from "async-lock"
import sizeOf from "image-size"
import { AnalyzeDocumentChain, loadSummarizationChain } from "langchain/chains"
import { OpenAI } from "langchain/llms/openai"
import { PromptTemplate } from "langchain/prompts"
@ -7,7 +10,7 @@ import removeMarkdown from "remove-markdown"
import { Metadata } from "@prisma/client"
import { QueryClient } from "@tanstack/react-query"
import { toGateway } from "~/lib/ipfs-parser"
import { toCid, toGateway } from "~/lib/ipfs-parser"
import prisma from "~/lib/prisma.server"
import { cacheGet } from "~/lib/redis.server"
import * as pageModel from "~/models/page.model"
@ -35,6 +38,145 @@ export const fetchGetPage = async (
})
}
export const getImageDimensionByUri = async (uri: string) => {
return new Promise<{ width: number; height: number; uri: string }>(
(resolve, reject) => {
const options = url.parse(uri)
https.get(options, function (response) {
const chunks: Buffer[] = []
let totalLength = 0
const MAX_BUFFER_LENGTH = 10240
response
.on("data", function (chunk) {
chunks.push(chunk)
totalLength += chunk.length
if (totalLength > MAX_BUFFER_LENGTH) {
response.destroy()
const buffer = Buffer.concat(chunks)
try {
const dimension = sizeOf(buffer)
if (dimension.width && dimension.height) {
resolve({
width: dimension.width,
height: dimension.height,
uri,
})
}
} catch (e) {
reject("Not enough data for image size detection.")
}
}
})
.on("end", function () {
if (totalLength <= MAX_BUFFER_LENGTH) {
const buffer = Buffer.concat(chunks)
try {
const dimension = sizeOf(buffer)
if (dimension.width && dimension.height) {
resolve({
width: dimension.width,
height: dimension.height,
uri,
})
}
} catch (e) {
reject(e)
}
}
})
})
},
)
}
export const cacheCoverDimensions = async ({
cid,
uris,
}: {
cid: string
uris: string[]
}) => {
if (!uris.length) return
const dimensions = (await cacheGet({
key: ["coverDimensions", cid],
allowEmpty: true,
noUpdate: true,
noExpire: true,
getValueFun: async () => {
let result
await lock.acquire(`coverDimensions_${cid}`, async () => {
const meta = await prisma.metadata.findFirst({
where: {
uri: `ipfs://${cid}`,
},
})
const key: keyof Metadata = "image_dimensions"
const dimensions = meta?.image_dimensions
if (dimensions) {
result = dimensions
} else {
const newDimensions: Record<
string,
{ width: number; height: number }
> = {}
console.time(`get image dimensions ${cid}`)
for (const uri of uris) {
const dimension = await getImageDimensionByUri(toGateway(uri))
if (dimension) {
newDimensions[uri] = dimension
}
}
console.timeEnd(`get image dimensions ${cid}`)
if (meta) {
await prisma.metadata.update({
where: { uri: `ipfs://${cid}` },
data: {
[key]: newDimensions,
},
})
} else {
await prisma.metadata.create({
data: {
uri: `ipfs://${cid}`,
[key]: newDimensions,
},
})
}
result = newDimensions
}
})
return result
},
})) as Record<string, { width: number; height: number }> | undefined
return dimensions
}
export const decoratePageForImageDimensions = async (
page?: Awaited<ReturnType<typeof pageModel.getPage>> | null,
) => {
if (!page) return
const dimensions = await cacheCoverDimensions({
cid: toCid(page?.metadata?.uri || ""),
uris: page?.metadata?.content?.images || [],
})
if (dimensions && Object.keys(dimensions).length && page?.metadata?.content) {
page.metadata.content["imageDimensions"] = dimensions
}
}
export const prefetchGetPagesBySite = async (
input: Parameters<typeof pageModel.getPagesBySite>[0],
queryClient: QueryClient,