mirror of
https://github.com/jakejarvis/sofa.git
synced 2026-10-16 06:25:42 -04:00
Migrate the entire monorepo from Biome 2.4.7 to oxlint 1.56.0 (linter) and oxfmt 0.41.0 (formatter) for faster lint/format and broader rule coverage. - Add `.oxlintrc.json` with React, TypeScript, unicorn, import plugins and correctness/suspicious categories - Add `.oxfmtrc.json` with 2-space indent, import sorting, and Tailwind class sorting (all 30+ custom className attributes migrated) - Add `docs/.oxlintrc.json` and `docs/.oxfmtrc.json` with Next.js plugin - Update all 12 workspace package.json scripts: `oxlint`, `oxfmt`, `oxfmt --check` - Add `format:check` turbo task and CI step - Update VS Code settings/extensions to use `oxc.oxc-vscode` - Update CI path triggers from `biome.json` to new config files - Remove all `biome-ignore` comments and fix shadowed variables - Delete `biome.json` and `docs/biome.json` - Reformat entire codebase with oxfmt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
90 lines
2.9 KiB
TypeScript
90 lines
2.9 KiB
TypeScript
import path from "node:path";
|
|
|
|
import sharp from "sharp";
|
|
import { rgbaToThumbHash } from "thumbhash";
|
|
|
|
import { db } from "@sofa/db/client";
|
|
import { eq } from "@sofa/db/helpers";
|
|
import { episodes, persons, seasons, titles } from "@sofa/db/schema";
|
|
import { createLogger } from "@sofa/logger";
|
|
import type { ImageCategory } from "@sofa/tmdb/image";
|
|
|
|
import { loadImageBuffer } from "./image-cache";
|
|
|
|
const log = createLogger("thumbhash");
|
|
|
|
/**
|
|
* Generate a ThumbHash string from an image path.
|
|
* Returns a base64-encoded ThumbHash, or null on failure.
|
|
*/
|
|
export async function generateThumbHash(
|
|
imagePath: string,
|
|
category: ImageCategory,
|
|
sourceBuffer?: Buffer | null,
|
|
): Promise<string | null> {
|
|
const filename = path.basename(imagePath);
|
|
const source =
|
|
sourceBuffer === undefined ? await loadImageBuffer(imagePath, category) : sourceBuffer;
|
|
if (!source) return null;
|
|
|
|
try {
|
|
const { data, info } = await sharp(source)
|
|
.resize(100, 100, { fit: "inside" })
|
|
.ensureAlpha()
|
|
.raw()
|
|
.toBuffer({ resolveWithObject: true });
|
|
|
|
const hash = rgbaToThumbHash(info.width, info.height, data);
|
|
return Buffer.from(hash).toString("base64");
|
|
} catch (err) {
|
|
log.error(`Failed to generate thumbhash for ${category}/${filename}:`, err);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function generateTitlePosterThumbHash(
|
|
titleId: string,
|
|
posterPath: string | null,
|
|
sourceBuffer?: Buffer | null,
|
|
): Promise<string | null> {
|
|
const hash = posterPath ? await generateThumbHash(posterPath, "posters", sourceBuffer) : null;
|
|
db.update(titles).set({ posterThumbHash: hash }).where(eq(titles.id, titleId)).run();
|
|
return hash;
|
|
}
|
|
|
|
export async function generateTitleBackdropThumbHash(
|
|
titleId: string,
|
|
backdropPath: string | null,
|
|
): Promise<string | null> {
|
|
const hash = backdropPath ? await generateThumbHash(backdropPath, "backdrops") : null;
|
|
db.update(titles).set({ backdropThumbHash: hash }).where(eq(titles.id, titleId)).run();
|
|
return hash;
|
|
}
|
|
|
|
export async function generateSeasonThumbHash(
|
|
seasonId: string,
|
|
posterPath: string | null,
|
|
): Promise<string | null> {
|
|
const hash = posterPath ? await generateThumbHash(posterPath, "posters") : null;
|
|
db.update(seasons).set({ posterThumbHash: hash }).where(eq(seasons.id, seasonId)).run();
|
|
return hash;
|
|
}
|
|
|
|
export async function generateEpisodeThumbHash(
|
|
episodeId: string,
|
|
stillPath: string | null,
|
|
): Promise<string | null> {
|
|
const hash = stillPath ? await generateThumbHash(stillPath, "stills") : null;
|
|
db.update(episodes).set({ stillThumbHash: hash }).where(eq(episodes.id, episodeId)).run();
|
|
return hash;
|
|
}
|
|
|
|
export async function generatePersonThumbHash(
|
|
personId: string,
|
|
profilePath: string | null,
|
|
): Promise<string | null> {
|
|
const hash = profilePath ? await generateThumbHash(profilePath, "profiles") : null;
|
|
db.update(persons).set({ profileThumbHash: hash }).where(eq(persons.id, personId)).run();
|
|
return hash;
|
|
}
|