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
This commit is contained in:
Cohee 2025-01-05 00:38:50 +02:00 committed by GitHub
parent 7f5dc72161
commit 8623d1198d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 71 additions and 17 deletions

View File

@ -83,13 +83,21 @@ autorun: true
# Avoids using 'localhost' for autorun in auto mode.
# use if you don't have 'localhost' in your hosts file
avoidLocalhost: false
# Disable thumbnail generation
disableThumbnails: false
# Thumbnail quality (0-100)
thumbnailsQuality: 95
# Generate avatar thumbnails as PNG instead of JPG (preserves transparency but increases filesize by about 100%)
# Changing this only affects new thumbnails. To recreate the old ones, clear out your ST/thumbnails/ folder.
avatarThumbnailsPng: false
# THUMBNAILING CONFIGURATION
thumbnails:
# Enable thumbnail generation
enabled: true
# Image format of avatar thumbnails:
# * "jpg": best compression with adjustable quality, no transparency
# * "png": preserves transparency but increases filesize by about 100%
# Changing this only affects new thumbnails. To recreate the old ones, clear out /thumbnails folder in your user data.
format: "jpg"
# JPG thumbnail quality (0-100)
quality: 95
# Maximum thumbnail dimensions per type [width, height]
dimensions: { 'bg': [160, 90], 'avatar': [96, 144] }
# Allow secret keys exposure via API
allowKeysExposure: false
# Skip new default content checks

View File

@ -28,6 +28,24 @@ const color = {
white: (mess) => color.byNum(mess, 37),
};
const keyMigrationMap = [
{
oldKey: 'disableThumbnails',
newKey: 'thumbnails.enabled',
migrate: (value) => !value,
},
{
oldKey: 'thumbnailsQuality',
newKey: 'thumbnails.quality',
migrate: (value) => value,
},
{
oldKey: 'avatarThumbnailsPng',
newKey: 'thumbnails.format',
migrate: (value) => (value ? 'png' : 'jpg'),
},
];
/**
* Gets all keys from an object recursively.
* @param {object} obj Object to get all keys from
@ -83,6 +101,24 @@ function addMissingConfigValues() {
const defaultConfig = yaml.parse(fs.readFileSync(path.join(process.cwd(), './default/config.yaml'), 'utf8'));
let config = yaml.parse(fs.readFileSync(path.join(process.cwd(), './config.yaml'), 'utf8'));
// Migrate old keys to new keys
const migratedKeys = [];
for (const { oldKey, newKey, migrate } of keyMigrationMap) {
if (_.has(config, oldKey)) {
const oldValue = _.get(config, oldKey);
const newValue = migrate(oldValue);
_.set(config, newKey, newValue);
_.unset(config, oldKey);
migratedKeys.push({
oldKey,
newKey,
oldValue,
newValue,
});
}
}
// Get all keys from the original config
const originalKeys = getAllKeys(config);
@ -95,11 +131,18 @@ function addMissingConfigValues() {
// Find the keys that were added
const addedKeys = _.difference(updatedKeys, originalKeys);
if (addedKeys.length === 0) {
if (addedKeys.length === 0 && migratedKeys.length === 0) {
return;
}
console.log('Adding missing config values to config.yaml:', addedKeys);
if (addedKeys.length > 0) {
console.log('Adding missing config values to config.yaml:', addedKeys);
}
if (migratedKeys.length > 0) {
console.log('Migrating config values in config.yaml:', migratedKeys);
}
fs.writeFileSync('./config.yaml', yaml.stringify(config));
} catch (error) {
console.error(color.red('FATAL: Could not add missing config values to config.yaml'), error);

View File

@ -12,9 +12,12 @@ import { getAllUserHandles, getUserDirectories } from '../users.js';
import { getConfigValue } from '../util.js';
import { jsonParser } from '../express-common.js';
const thumbnailsDisabled = getConfigValue('disableThumbnails', false);
const quality = getConfigValue('thumbnailsQuality', 95);
const pngFormat = getConfigValue('avatarThumbnailsPng', false);
const thumbnailsEnabled = !!getConfigValue('thumbnails.enabled', true);
const quality = Math.min(100, Math.max(1, parseInt(getConfigValue('thumbnails.quality', 95))));
const pngFormat = String(getConfigValue('thumbnails.format', 'jpg')).toLowerCase().trim() === 'png';
/** @type {Record<string, number[]>} */
const dimensions = getConfigValue('thumbnails.dimensions', { 'bg': [160, 90], 'avatar': [96, 144] });
/**
* Gets a path to thumbnail folder based on the type.
@ -114,16 +117,16 @@ async function generateThumbnail(directories, type, file) {
return null;
}
const imageSizes = { 'bg': [160, 90], 'avatar': [96, 144] };
const mySize = imageSizes[type];
try {
let buffer;
try {
const size = dimensions[type];
const image = await jimp.read(pathToOriginalFile);
const imgType = type == 'avatar' && pngFormat ? 'image/png' : 'image/jpeg';
buffer = await image.cover(mySize[0], mySize[1]).quality(quality).getBufferAsync(imgType);
const width = !isNaN(size?.[0]) && size?.[0] > 0 ? size[0] : image.bitmap.width;
const height = !isNaN(size?.[1]) && size?.[1] > 0 ? size[1] : image.bitmap.height;
buffer = await image.cover(width, height).quality(quality).getBufferAsync(imgType);
}
catch (inner) {
console.warn(`Thumbnailer can not process the image: ${pathToOriginalFile}. Using original size`);
@ -193,7 +196,7 @@ router.get('/', jsonParser, async function (request, response) {
return response.sendStatus(403);
}
if (thumbnailsDisabled) {
if (!thumbnailsEnabled) {
const folder = getOriginalFolder(request.user.directories, type);
if (folder === undefined) {