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
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. # Avoids using 'localhost' for autorun in auto mode.
# use if you don't have 'localhost' in your hosts file # use if you don't have 'localhost' in your hosts file
avoidLocalhost: false avoidLocalhost: false
# Disable thumbnail generation
disableThumbnails: false # THUMBNAILING CONFIGURATION
# Thumbnail quality (0-100) thumbnails:
thumbnailsQuality: 95 # Enable thumbnail generation
# Generate avatar thumbnails as PNG instead of JPG (preserves transparency but increases filesize by about 100%) enabled: true
# Changing this only affects new thumbnails. To recreate the old ones, clear out your ST/thumbnails/ folder. # Image format of avatar thumbnails:
avatarThumbnailsPng: false # * "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 # Allow secret keys exposure via API
allowKeysExposure: false allowKeysExposure: false
# Skip new default content checks # Skip new default content checks

View File

@@ -28,6 +28,24 @@ const color = {
white: (mess) => color.byNum(mess, 37), 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. * Gets all keys from an object recursively.
* @param {object} obj Object to get all keys from * @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')); 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')); 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 // Get all keys from the original config
const originalKeys = getAllKeys(config); const originalKeys = getAllKeys(config);
@@ -95,11 +131,18 @@ function addMissingConfigValues() {
// Find the keys that were added // Find the keys that were added
const addedKeys = _.difference(updatedKeys, originalKeys); const addedKeys = _.difference(updatedKeys, originalKeys);
if (addedKeys.length === 0) { if (addedKeys.length === 0 && migratedKeys.length === 0) {
return; 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)); fs.writeFileSync('./config.yaml', yaml.stringify(config));
} catch (error) { } catch (error) {
console.error(color.red('FATAL: Could not add missing config values to config.yaml'), 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 { getConfigValue } from '../util.js';
import { jsonParser } from '../express-common.js'; import { jsonParser } from '../express-common.js';
const thumbnailsDisabled = getConfigValue('disableThumbnails', false); const thumbnailsEnabled = !!getConfigValue('thumbnails.enabled', true);
const quality = getConfigValue('thumbnailsQuality', 95); const quality = Math.min(100, Math.max(1, parseInt(getConfigValue('thumbnails.quality', 95))));
const pngFormat = getConfigValue('avatarThumbnailsPng', false); 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. * Gets a path to thumbnail folder based on the type.
@@ -114,16 +117,16 @@ async function generateThumbnail(directories, type, file) {
return null; return null;
} }
const imageSizes = { 'bg': [160, 90], 'avatar': [96, 144] };
const mySize = imageSizes[type];
try { try {
let buffer; let buffer;
try { try {
const size = dimensions[type];
const image = await jimp.read(pathToOriginalFile); const image = await jimp.read(pathToOriginalFile);
const imgType = type == 'avatar' && pngFormat ? 'image/png' : 'image/jpeg'; 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) { catch (inner) {
console.warn(`Thumbnailer can not process the image: ${pathToOriginalFile}. Using original size`); 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); return response.sendStatus(403);
} }
if (thumbnailsDisabled) { if (!thumbnailsEnabled) {
const folder = getOriginalFolder(request.user.directories, type); const folder = getOriginalFolder(request.user.directories, type);
if (folder === undefined) { if (folder === undefined) {