Add thumbnail dimensions to config (#3262) * Add thumbnail dimensions to config * Fix default value for thumbnails.enabled * Update comment for thumbnail recreation instructions in config.yaml * Lint config values * Verify config size > 0 * More config lint

8623d1198d75f971ee8547271a7daeee24f7b92d

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
3 files changed, +71 -17Ignore whitespace
default/config.yaml+15 -7
@@ -83,13 +83,21 @@ autorun: true
8383# Avoids using 'localhost' for autorun in auto mode.
8484# use if you don't have 'localhost' in your hosts file
8585avoidLocalhost: false
86-# Disable thumbnail generation
86+
87-disableThumbnails: false
87+# THUMBNAILING CONFIGURATION
88-# Thumbnail quality (0-100)
88+thumbnails:
89-thumbnailsQuality: 95
89+ # Enable thumbnail generation
90-# Generate avatar thumbnails as PNG instead of JPG (preserves transparency but increases filesize by about 100%)
90+ enabled: true
91-# Changing this only affects new thumbnails. To recreate the old ones, clear out your ST/thumbnails/ folder.
91+ # Image format of avatar thumbnails:
92-avatarThumbnailsPng: false
92+ # * "jpg": best compression with adjustable quality, no transparency
93+ # * "png": preserves transparency but increases filesize by about 100%
94+ # Changing this only affects new thumbnails. To recreate the old ones, clear out /thumbnails folder in your user data.
95+ format: "jpg"
96+ # JPG thumbnail quality (0-100)
97+ quality: 95
98+ # Maximum thumbnail dimensions per type [width, height]
99+ dimensions: { 'bg': [160, 90], 'avatar': [96, 144] }
100+
93101# Allow secret keys exposure via API
94102allowKeysExposure: false
95103# Skip new default content checks
post-install.js+45 -2
@@ -28,6 +28,24 @@ const color = {
2828 white: (mess) => color.byNum(mess, 37),
2929};
3030
31+const keyMigrationMap = [
32+ {
33+ oldKey: 'disableThumbnails',
34+ newKey: 'thumbnails.enabled',
35+ migrate: (value) => !value,
36+ },
37+ {
38+ oldKey: 'thumbnailsQuality',
39+ newKey: 'thumbnails.quality',
40+ migrate: (value) => value,
41+ },
42+ {
43+ oldKey: 'avatarThumbnailsPng',
44+ newKey: 'thumbnails.format',
45+ migrate: (value) => (value ? 'png' : 'jpg'),
46+ },
47+];
48+
3149/**
3250 * Gets all keys from an object recursively.
3351 * @param {object} obj Object to get all keys from
@@ -83,6 +101,24 @@ function addMissingConfigValues() {
83101 const defaultConfig = yaml.parse(fs.readFileSync(path.join(process.cwd(), './default/config.yaml'), 'utf8'));
84102 let config = yaml.parse(fs.readFileSync(path.join(process.cwd(), './config.yaml'), 'utf8'));
85103
104+ // Migrate old keys to new keys
105+ const migratedKeys = [];
106+ for (const { oldKey, newKey, migrate } of keyMigrationMap) {
107+ if (_.has(config, oldKey)) {
108+ const oldValue = _.get(config, oldKey);
109+ const newValue = migrate(oldValue);
110+ _.set(config, newKey, newValue);
111+ _.unset(config, oldKey);
112+
113+ migratedKeys.push({
114+ oldKey,
115+ newKey,
116+ oldValue,
117+ newValue,
118+ });
119+ }
120+ }
121+
86122 // Get all keys from the original config
87123 const originalKeys = getAllKeys(config);
88124
@@ -95,11 +131,18 @@ function addMissingConfigValues() {
95131 // Find the keys that were added
96132 const addedKeys = _.difference(updatedKeys, originalKeys);
97133
98134 if (addedKeys.length === 0 && migratedKeys.length === 0) {
99135 return;
100136 }
101137
102- console.log('Adding missing config values to config.yaml:', addedKeys);
138+ if (addedKeys.length > 0) {
139+ console.log('Adding missing config values to config.yaml:', addedKeys);
140+ }
141+
142+ if (migratedKeys.length > 0) {
143+ console.log('Migrating config values in config.yaml:', migratedKeys);
144+ }
145+
103146 fs.writeFileSync('./config.yaml', yaml.stringify(config));
104147 } catch (error) {
105148 console.error(color.red('FATAL: Could not add missing config values to config.yaml'), error);
src/endpoints/thumbnails.js+11 -8
@@ -12,9 +12,12 @@ import { getAllUserHandles, getUserDirectories } from '../users.js';
1212import { getConfigValue } from '../util.js';
1313import { jsonParser } from '../express-common.js';
1414
1515const thumbnailsDisabledthumbnailsEnabled = !!getConfigValue('disableThumbnailsthumbnails.enabled', falsetrue);
1616const quality = Math.min(100, Math.max(1, parseInt(getConfigValue('thumbnailsQualitythumbnails.quality', 95))));
1717const pngFormat = String(getConfigValue('avatarThumbnailsPngthumbnails.format', false'jpg')).toLowerCase().trim() === 'png';
18+
19+/** @type {Record<string, number[]>} */
20+const dimensions = getConfigValue('thumbnails.dimensions', { 'bg': [160, 90], 'avatar': [96, 144] });
1821
1922/**
2023 * Gets a path to thumbnail folder based on the type.
@@ -114,16 +117,16 @@ async function generateThumbnail(directories, type, file) {
114117 return null;
115118 }
116119
117- const imageSizes = { 'bg': [160, 90], 'avatar': [96, 144] };
118- const mySize = imageSizes[type];
119-
120120 try {
121121 let buffer;
122122
123123 try {
124+ const size = dimensions[type];
124125 const image = await jimp.read(pathToOriginalFile);
125126 const imgType = type == 'avatar' && pngFormat ? 'image/png' : 'image/jpeg';
126- buffer = await image.cover(mySize[0], mySize[1]).quality(quality).getBufferAsync(imgType);
127+ const width = !isNaN(size?.[0]) && size?.[0] > 0 ? size[0] : image.bitmap.width;
128+ const height = !isNaN(size?.[1]) && size?.[1] > 0 ? size[1] : image.bitmap.height;
129+ buffer = await image.cover(width, height).quality(quality).getBufferAsync(imgType);
127130 }
128131 catch (inner) {
129132 console.warn(`Thumbnailer can not process the image: ${pathToOriginalFile}. Using original size`);
@@ -193,7 +196,7 @@ router.get('/', jsonParser, async function (request, response) {
193196 return response.sendStatus(403);
194197 }
195198
196199 if (thumbnailsDisabled!thumbnailsEnabled) {
197200 const folder = getOriginalFolder(request.user.directories, type);
198201
199202 if (folder === undefined) {