First thumbnail attempt

This commit is contained in:
Stefan Forstenlechner 2022-04-10 20:49:19 +02:00
parent b6ddbfb92b
commit 1034df02f2
9 changed files with 1079 additions and 9 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
.idea .idea
public/.thumbnail
node_modules node_modules
build build

View File

@ -26,7 +26,7 @@
}, },
"scripts": { "scripts": {
"start-client": "react-scripts start", "start-client": "react-scripts start",
"build-client": "react-scripts build", "build-client": "react-scripts build && npm run set-environment",
"test-client": "react-scripts test", "test-client": "react-scripts test",
"eject-client": "react-scripts eject", "eject-client": "react-scripts eject",
"set-environment": "npx react-inject-env set", "set-environment": "npx react-inject-env set",

View File

@ -23,7 +23,8 @@
"ts": "never", "ts": "never",
"tsx": "never" "tsx": "never"
} }
] ],
"import/prefer-default-export": "off"
}, },
"settings": { "settings": {
"import/resolver": { "import/resolver": {

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/sharp": "^0.30.1",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/eslint-plugin": "^5.18.0",
"@typescript-eslint/parser": "^5.18.0", "@typescript-eslint/parser": "^5.18.0",
@ -33,6 +34,7 @@
"express": "^4.17.3", "express": "^4.17.3",
"image-size": "^1.0.1", "image-size": "^1.0.1",
"nodemon": "^2.0.15", "nodemon": "^2.0.15",
"sharp": "^0.30.3",
"ts-node": "^10.7.0" "ts-node": "^10.7.0"
} }
} }

View File

@ -3,7 +3,12 @@ import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import sizeOf from "image-size"; import sizeOf from "image-size";
import { a, Folder, Folders, Image } from "./models"; import { a, Folder, Folders, Image } from "./models";
import { publicPath, walk } from "./fsExtension"; import { walk } from "./fsExtension";
import {
createThumbnailAsyncForImage,
initThumbnailsAsync,
} from "./thumbnails";
import { publicPath, thumbnailPath, thumbnailPublicPath } from "./paths";
const app = express(); const app = express();
@ -19,15 +24,23 @@ app.get("/api(/*)?", (req, res) => {
const dirents = fs.readdirSync(publicPath + requestedPath, { const dirents = fs.readdirSync(publicPath + requestedPath, {
withFileTypes: true, withFileTypes: true,
}); });
const thumbnails = fs.readdirSync(thumbnailPublicPath + requestedPath);
const normalizedPath = requestedPath === "/" ? "" : requestedPath; const normalizedPath = requestedPath === "/" ? "" : requestedPath;
const images: Image[] = dirents const images: Image[] = dirents
.filter((f) => f.isFile()) .filter((f) => f.isFile())
.map((f) => { .map((f) => {
const thumbnailExists: boolean = thumbnails.includes(f.name);
if (!thumbnailExists) {
createThumbnailAsyncForImage(`${requestedPath}/${f.name}`);
}
const dimensions = sizeOf(`${publicPath}${requestedPath}/${f.name}`); const dimensions = sizeOf(`${publicPath}${requestedPath}/${f.name}`);
const widthAndHeightSwap = dimensions.orientation > 4; // see https://exiftool.org/TagNames/EXIF.html const widthAndHeightSwap = dimensions.orientation > 4; // see https://exiftool.org/TagNames/EXIF.html
return { return {
src: `/images${normalizedPath}/${f.name}`, src: thumbnailExists
? `/images${thumbnailPath}${normalizedPath}/${f.name}`
: `/images${normalizedPath}/${f.name}`,
width: widthAndHeightSwap ? dimensions.height : dimensions.width, width: widthAndHeightSwap ? dimensions.height : dimensions.width,
height: widthAndHeightSwap ? dimensions.width : dimensions.height, height: widthAndHeightSwap ? dimensions.width : dimensions.height,
}; };
@ -37,7 +50,7 @@ app.get("/api(/*)?", (req, res) => {
}); });
app.get("/directories", (req, res) => { app.get("/directories", (req, res) => {
res.json(a<Folders>(walk("/"))); res.json(a<Folders>(walk("")));
}); });
// All other GET requests not handled before will return our React app // All other GET requests not handled before will return our React app
@ -48,6 +61,10 @@ app.get("*", (req, res) => {
}); });
app.listen(PORT, () => { app.listen(PORT, () => {
// eslint-disable-next-line no-console /* eslint-disable no-console */
console.log(`Start processing thumbnails async`);
initThumbnailsAsync("");
return console.log(`Express is listening at http://localhost:${PORT}`); return console.log(`Express is listening at http://localhost:${PORT}`);
/* eslint-enable no-console */
}); });

View File

@ -1,11 +1,12 @@
import fs from "fs"; import fs from "fs";
import * as path from "path"; import * as path from "path";
import { Folders } from "./models"; import { Folders } from "./models";
import { publicPath, thumbnailPath } from "./paths";
export const publicPath = "../public";
export const walk = (dirPath: string): Folders => { export const walk = (dirPath: string): Folders => {
const dirEnts = fs.readdirSync(publicPath + dirPath, { withFileTypes: true }); const dirEnts = fs.readdirSync(`${publicPath}/${dirPath}`, {
withFileTypes: true,
});
return { return {
name: path.basename(dirPath) || "Home", name: path.basename(dirPath) || "Home",
@ -13,6 +14,7 @@ export const walk = (dirPath: string): Folders => {
numberOfFiles: dirEnts.filter((f) => f.isFile()).length, numberOfFiles: dirEnts.filter((f) => f.isFile()).length,
children: dirEnts children: dirEnts
.filter((d) => d.isDirectory()) .filter((d) => d.isDirectory())
.filter((d) => !d.name.includes(thumbnailPath.substring(1)))
.map((d) => walk(path.posix.join(dirPath, d.name))), .map((d) => walk(path.posix.join(dirPath, d.name))),
}; };
}; };

View File

@ -0,0 +1,3 @@
export const publicPath = "../public";
export const thumbnailPath = `/.thumbnail`;
export const thumbnailPublicPath = `${publicPath}${thumbnailPath}`;

View File

@ -0,0 +1,48 @@
import sharp from "sharp";
import fs from "fs";
import path from "path";
import { publicPath, thumbnailPath, thumbnailPublicPath } from "./paths";
const percentage = 25;
const minimumPixelForThumbnail = 1000;
export const createThumbnailAsyncForImage = (image: string) => {
sharp(`${publicPath}${image}`)
.metadata()
.then((info) => {
let width = Math.round((info.width * percentage) / 100);
let height = Math.round((info.height * percentage) / 100);
// no thumbnail if both sides are smaller than minimumPixelForThumbnail
if (
info.width <= minimumPixelForThumbnail &&
info.height <= minimumPixelForThumbnail
) {
width = info.width;
height = info.height;
}
fs.mkdir(
thumbnailPublicPath + path.dirname(image),
{ recursive: true },
() => {
sharp(`${publicPath}${image}`)
.resize(width, height)
.toFile(`${thumbnailPublicPath}${image}`);
}
);
});
};
export const initThumbnailsAsync = (dirPath: string) => {
if (dirPath.includes(thumbnailPath)) {
return;
}
const dirEnts = fs.readdirSync(publicPath + dirPath, { withFileTypes: true });
dirEnts
.filter((f) => f.isFile())
.map((f) => createThumbnailAsyncForImage(`${dirPath}/${f.name}`));
dirEnts
.filter((f) => f.isDirectory())
.forEach((d) => initThumbnailsAsync(`${dirPath}/${d.name}`));
};