From 1a359fd6f0a4f672db3e50ee7d5398f223124edb Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 27 May 2023 11:13:18 -0300 Subject: thumbs: get identify binary in addition to convert --- src/gen-thumbs.js | 55 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 20 deletions(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index e9932822..0846dd6f 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -122,8 +122,8 @@ function readFileMD5(filePath) { }); } -async function getImageMagickVersion(spawnConvert) { - const proc = spawnConvert(['--version'], false); +async function getImageMagickVersion(binary) { + const proc = spawn(binary, ['--version']); let allData = ''; proc.stdout.on('data', (data) => { @@ -144,23 +144,33 @@ async function getImageMagickVersion(spawnConvert) { return match[1]; } -async function getSpawnConvert() { - let fn, description, version; - if (await commandExists('convert')) { - fn = (args) => spawn('convert', args); - description = 'convert'; - } else if (await commandExists('magick')) { - fn = (args, prefix = true) => - spawn('magick', prefix ? ['convert', ...args] : args); - description = 'magick convert'; - } else { - return [`no convert or magick binary`, null]; +async function getSpawnMagick(tool) { + if (tool !== 'identify' && tool !== 'convert') { + throw new Error(`Expected identify or convert`); } - version = await getImageMagickVersion(fn); + let fn = null; + let description = null; + let version = null; - if (version === null) { - return [`binary --version output didn't indicate it's ImageMagick`]; + if (await commandExists(tool)) { + version = await getImageMagickVersion(tool); + if (version !== null) { + fn = (args) => spawn(tool, args); + description = tool; + } + } + + if (fn === null && await commandExists('magick')) { + version = await getImageMagickVersion(fn); + if (version !== null) { + fn = (args) => spawn('magick', [tool, ...args]); + description = `magick ${tool}`; + } + } + + if (fn === null) { + return [`no ${tool} or magick binary`, null]; } return [`${description} (${version})`, fn]; @@ -290,18 +300,23 @@ export default async function genThumbs(mediaPath, { const quietInfo = quiet ? () => null : logInfo; - const [convertInfo, spawnConvert] = (await getSpawnConvert()) ?? []; - if (!spawnConvert) { + const [convertInfo, spawnConvert] = await getSpawnMagick('convert'); + const [identifyInfo, spawnIdentify] = await getSpawnMagick('convert'); + + if (!spawnConvert || !spawnIdentify) { logError`${`It looks like you don't have ImageMagick installed.`}`; logError`ImageMagick is required to generate thumbnails for display on the wiki.`; - logError`(Error message: ${convertInfo})`; + for (const error of [convertInfo, identifyInfo].filter(Boolean)) { + logError`(Error message: ${error})`; + } logInfo`You can find info to help install ImageMagick on Linux, Windows, or macOS`; logInfo`from its official website: ${`https://imagemagick.org/script/download.php`}`; logInfo`If you have trouble working ImageMagick and would like some help, feel free`; logInfo`to drop a message in the HSMusic Discord server! ${'https://hsmusic.wiki/discord/'}`; return false; } else { - logInfo`Found ImageMagick binary: ${convertInfo}`; + logInfo`Found ImageMagick convert binary: ${convertInfo}`; + logInfo`Found ImageMagick identify binary: ${identifyInfo}`; } quietInfo`Running up to ${magickThreads + ' magick threads'} simultaneously.`; -- cgit 1.3.0-6-gf8a5 From 0944e67a92b2ac7203af1e7152a33395b32923a2 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 27 May 2023 12:08:26 -0300 Subject: thumbs: imagemagick is fricking killing me --- src/gen-thumbs.js | 111 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 90 insertions(+), 21 deletions(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index 0846dd6f..2f5304ea 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -122,6 +122,48 @@ function readFileMD5(filePath) { }); } +async function identifyImageDimensions(filePath, {spawnIdentify}) { + const maxTries = 5; + + const recursive = async n => { + if (n > maxTries) { + throw new Error(`Didn't get any output after ${maxTries} tries`); + } + + if (n > 1) { + logInfo`Attempt #${n} for ${filePath}`; + } + + const stdoutText = await new Promise((resolve, reject) => { + let stdout = ''; + let stderr = ''; + + const proc = spawnIdentify(['-format', '%w %h', filePath]); + proc.stdout.on('data', data => stdout += data); + proc.stderr.on('data', data => stderr += data); + + proc.on('exit', code => { + if (code === 0) { + resolve(stdout); + } else { + reject(stderr); + } + }); + }); + + if (stdoutText === '') { + return recursive(n + 1); + } + + const words = stdoutText.split(' '); + const width = parseInt(words[0]); + const height = parseInt(words[1]); + return [width, height]; + }; + + return recursive(1); +} + async function getImageMagickVersion(binary) { const proc = spawn(binary, ['--version']); @@ -301,7 +343,7 @@ export default async function genThumbs(mediaPath, { const quietInfo = quiet ? () => null : logInfo; const [convertInfo, spawnConvert] = await getSpawnMagick('convert'); - const [identifyInfo, spawnIdentify] = await getSpawnMagick('convert'); + const [identifyInfo, spawnIdentify] = await getSpawnMagick('identify'); if (!spawnConvert || !spawnIdentify) { logError`${`It looks like you don't have ImageMagick installed.`}`; @@ -313,7 +355,7 @@ export default async function genThumbs(mediaPath, { logInfo`from its official website: ${`https://imagemagick.org/script/download.php`}`; logInfo`If you have trouble working ImageMagick and would like some help, feel free`; logInfo`to drop a message in the HSMusic Discord server! ${'https://hsmusic.wiki/discord/'}`; - return false; + return {success: false}; } else { logInfo`Found ImageMagick convert binary: ${convertInfo}`; logInfo`Found ImageMagick identify binary: ${identifyInfo}`; @@ -356,19 +398,16 @@ export default async function genThumbs(mediaPath, { const imagePaths = await traverseSourceImagePaths(mediaPath, {target: 'generate'}); - const imageToMD5Entries = await progressPromiseAll( - `Generating MD5s of image files`, - queue( - imagePaths.map( - (imagePath) => () => - readFileMD5(path.join(mediaPath, imagePath)).then( - (md5) => [imagePath, md5], - (error) => [imagePath, {error}] - ) - ), - queueSize - ) - ); + const imageToMD5Entries = + await progressPromiseAll( + `Generating MD5s of image files`, + queue( + imagePaths.map(imagePath => () => + readFileMD5(path.join(mediaPath, imagePath)) + .then( + md5 => [imagePath, md5], + error => [imagePath, {error}])), + queueSize)); { let error = false; @@ -382,23 +421,53 @@ export default async function genThumbs(mediaPath, { logError`Failed to read at least one image file!`; logError`This implies a thumbnail probably won't be generatable.`; logError`So, exiting early.`; - return false; + return {success: false}; } else { quietInfo`All image files successfully read.`; } } + const imageToDimensionsEntries = + await progressPromiseAll( + `Identifying dimensions of image files`, + queue( + imagePaths.map(imagePath => () => + identifyImageDimensions(path.join(mediaPath, imagePath), {spawnIdentify}) + .then( + dimensions => [imagePath, dimensions], + error => [imagePath, {error}])), + queueSize)); + + { + let error = false; + for (const entry of imageToDimensionsEntries) { + if (entry[1].error) { + logError`Failed to identify dimensions ${entry[0]}: ${entry[1].error}`; + error = true; + } + } + if (error) { + logError`Failed to identify dimensions of at least one image file!`; + logError`This implies a thumbnail probably won't be generatable.`; + logError`So, exiting early.`; + return {success: false}; + } else { + quietInfo`All image files successfully had dimensions identified.`; + } + } + + const imageToDimensions = Object.fromEntries(imageToDimensionsEntries); + // Technically we could pro8a8ly mut8te the cache varia8le in-place? // 8ut that seems kinda iffy. const updatedCache = Object.assign({}, cache); const entriesToGenerate = imageToMD5Entries.filter( - ([filePath, md5]) => md5 !== cache[filePath] - ); + ([filePath, md5]) => md5 !== cache[filePath]?.[0]); if (empty(entriesToGenerate)) { logInfo`All image thumbnails are already up-to-date - nice!`; - return true; + return {success: true, cache}; } logInfo`Generating thumbnails for ${entriesToGenerate.length} media files.`; @@ -416,7 +485,7 @@ export default async function genThumbs(mediaPath, { entriesToGenerate.map(([filePath, md5]) => () => generateImageThumbnails(path.join(mediaPath, filePath), {spawnConvert}).then( () => { - updatedCache[filePath] = md5; + updatedCache[filePath] = [md5, ...imageToDimensions[filePath]]; succeeded.push(filePath); }, error => { @@ -446,7 +515,7 @@ export default async function genThumbs(mediaPath, { logWarn`Sorry about that!`; } - return true; + return {success: true, cache: updatedCache}; } export function getExpectedImagePaths(mediaPath, {urls, wikiData}) { -- cgit 1.3.0-6-gf8a5 From d68ead9ff27a166ccf90492cd900ef4d8d6b8e3e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 27 May 2023 12:15:41 -0300 Subject: thumbs: use image-size module instead of magick identify --- src/gen-thumbs.js | 57 +++++++++++-------------------------------------------- 1 file changed, 11 insertions(+), 46 deletions(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index 2f5304ea..4f5c0fec 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -91,6 +91,8 @@ import {createReadStream} from 'node:fs'; import {readFile, stat, unlink, writeFile} from 'node:fs/promises'; import * as path from 'node:path'; +import dimensionsOf from 'image-size'; + import { color, fileIssue, @@ -122,46 +124,11 @@ function readFileMD5(filePath) { }); } -async function identifyImageDimensions(filePath, {spawnIdentify}) { - const maxTries = 5; - - const recursive = async n => { - if (n > maxTries) { - throw new Error(`Didn't get any output after ${maxTries} tries`); - } - - if (n > 1) { - logInfo`Attempt #${n} for ${filePath}`; - } - - const stdoutText = await new Promise((resolve, reject) => { - let stdout = ''; - let stderr = ''; - - const proc = spawnIdentify(['-format', '%w %h', filePath]); - proc.stdout.on('data', data => stdout += data); - proc.stderr.on('data', data => stderr += data); - - proc.on('exit', code => { - if (code === 0) { - resolve(stdout); - } else { - reject(stderr); - } - }); - }); - - if (stdoutText === '') { - return recursive(n + 1); - } - - const words = stdoutText.split(' '); - const width = parseInt(words[0]); - const height = parseInt(words[1]); - return [width, height]; - }; - - return recursive(1); +async function identifyImageDimensions(filePath) { + // See: https://github.com/image-size/image-size/issues/96 + const buffer = await readFile(filePath); + const dimensions = dimensionsOf(buffer); + return [dimensions.width, dimensions.height]; } async function getImageMagickVersion(binary) { @@ -343,12 +310,11 @@ export default async function genThumbs(mediaPath, { const quietInfo = quiet ? () => null : logInfo; const [convertInfo, spawnConvert] = await getSpawnMagick('convert'); - const [identifyInfo, spawnIdentify] = await getSpawnMagick('identify'); - if (!spawnConvert || !spawnIdentify) { + if (!spawnConvert) { logError`${`It looks like you don't have ImageMagick installed.`}`; logError`ImageMagick is required to generate thumbnails for display on the wiki.`; - for (const error of [convertInfo, identifyInfo].filter(Boolean)) { + for (const error of [convertInfo].filter(Boolean)) { logError`(Error message: ${error})`; } logInfo`You can find info to help install ImageMagick on Linux, Windows, or macOS`; @@ -357,8 +323,7 @@ export default async function genThumbs(mediaPath, { logInfo`to drop a message in the HSMusic Discord server! ${'https://hsmusic.wiki/discord/'}`; return {success: false}; } else { - logInfo`Found ImageMagick convert binary: ${convertInfo}`; - logInfo`Found ImageMagick identify binary: ${identifyInfo}`; + logInfo`Found ImageMagick binary: ${convertInfo}`; } quietInfo`Running up to ${magickThreads + ' magick threads'} simultaneously.`; @@ -432,7 +397,7 @@ export default async function genThumbs(mediaPath, { `Identifying dimensions of image files`, queue( imagePaths.map(imagePath => () => - identifyImageDimensions(path.join(mediaPath, imagePath), {spawnIdentify}) + identifyImageDimensions(path.join(mediaPath, imagePath)) .then( dimensions => [imagePath, dimensions], error => [imagePath, {error}])), -- cgit 1.3.0-6-gf8a5 From 641d821d03ad96bca28b8b09d2408457443a9f7f Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 30 May 2023 09:50:21 -0300 Subject: thumbs: only generate thumbs of appropriate sizes --- src/gen-thumbs.js | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 68 insertions(+), 5 deletions(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index 4f5c0fec..bf6de286 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -114,6 +114,60 @@ import {delay, empty, queue} from '#sugar'; export const defaultMagickThreads = 8; +export function getThumbnailsAvailableForDimensions([width, height]) { + // This function is intended to be portable, so it can be used both for + // calculating which thumbnails to generate, and which ones will be ready + // to reference in generated code. Sizes are in array [name, size] form + // with larger sizes earlier in return. Keep in mind this isn't a direct + // 1:1 mapping with the sizes listed in the thumbnail spec, because the + // largest thumbnail (first in return) will be adjusted to the provided + // dimensions. + + const {all} = getThumbnailsAvailableForDimensions; + + // Find the largest size which is beneath the passed dimensions. We use the + // longer edge here (of width and height) so that each resulting thumbnail is + // fully constrained within the size*size square defined by its spec. + const longerEdge = Math.max(width, height); + const index = all.findIndex(([name, size]) => size <= longerEdge); + + // Literal edge cases are handled specially. For dimensions which are bigger + // than the biggest thumbnail in the spec, return all possible results. + // These don't need any adjustments since the largest is already smaller than + // the provided dimensions. + if (index === 0) { + return [ + ...all, + ]; + } + + // For dimensions which are smaller than the smallest thumbnail, return only + // the smallest, adjusted to the provided dimensions. + if (index === -1) { + const smallest = all[all.length - 1]; + return [ + [smallest[0], longerEdge], + ]; + } + + // For non-edge cases, we return the largest size below the dimensions + // as well as everything smaller, but also the next size larger - that way + // there's a size which is as big as the original, but still JPEG compressed. + // The size larger is adjusted to the provided dimensions to represent the + // actual dimensions it'll provide. + const larger = all[index - 1]; + const rest = all.slice(index); + return [ + [larger[0], longerEdge], + ...rest, + ]; +} + +getThumbnailsAvailableForDimensions.all = + Object.entries(thumbnailSpec) + .map(([name, {size}]) => [name, size]) + .sort((a, b) => b[1] - a[1]); + function readFileMD5(filePath) { return new Promise((resolve, reject) => { const md5 = createHash('md5'); @@ -185,7 +239,11 @@ async function getSpawnMagick(tool) { return [`${description} (${version})`, fn]; } -function generateImageThumbnails(filePath, {spawnConvert}) { +function generateImageThumbnails({ + filePath, + dimensions, + spawnConvert, +}) { const dirname = path.dirname(filePath); const extname = path.extname(filePath); const basename = path.basename(filePath, extname); @@ -205,9 +263,10 @@ function generateImageThumbnails(filePath, {spawnConvert}) { ]); return Promise.all( - Object.entries(thumbnailSpec) - .map(([ext, details]) => - promisifyProcess(convert('.' + ext, details), false))); + getThumbnailsAvailableForDimensions(dimensions) + .map(([name]) => [name, thumbnailSpec[name]]) + .map(([name, details]) => + promisifyProcess(convert('.' + name, details), false))); } export async function clearThumbs(mediaPath, { @@ -448,7 +507,11 @@ export default async function genThumbs(mediaPath, { await progressPromiseAll(writeMessageFn, queue( entriesToGenerate.map(([filePath, md5]) => () => - generateImageThumbnails(path.join(mediaPath, filePath), {spawnConvert}).then( + generateImageThumbnails({ + filePath: path.join(mediaPath, filePath), + dimensions: imageToDimensions[filePath], + spawnConvert, + }).then( () => { updatedCache[filePath] = [md5, ...imageToDimensions[filePath]]; succeeded.push(filePath); -- cgit 1.3.0-6-gf8a5 From 029210cc329a015a939472a688209d3f3423242b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 30 May 2023 09:51:26 -0300 Subject: thumbs, content: integrate cached thumb sizes into content --- src/gen-thumbs.js | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index bf6de286..f6a8eaaf 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -74,7 +74,7 @@ 'use strict'; -const CACHE_FILE = 'thumbnail-cache.json'; +export const CACHE_FILE = 'thumbnail-cache.json'; const WARNING_DELAY_TIME = 10000; const thumbnailSpec = { @@ -168,6 +168,30 @@ getThumbnailsAvailableForDimensions.all = .map(([name, {size}]) => [name, size]) .sort((a, b) => b[1] - a[1]); +export function getDimensionsOfImagePath(imagePath, cache) { + // This function is really generic. It takes the gen-thumbs image cache and + // returns the dimensions in that cache, so that other functions don't need + // to hard-code the cache format. + + const [width, height] = cache[imagePath].slice(1); + return [width, height]; +} + +export function getThumbnailEqualOrSmaller(preferred, imagePath, cache) { + // This function is totally exclusive to page generation. It's a shorthand + // for accessing dimensions from the thumbnail cache, calculating all the + // thumbnails available, and selecting the one which is equal to or smaller + // than the provided size. Since the path provided might not be the actual + // one which is being thumbnail-ified, this just returns the name of the + // selected thumbnail size. + + const {size: preferredSize} = thumbnailSpec[preferred]; + const [width, height] = getDimensionsOfImagePath(imagePath, cache); + const available = getThumbnailsAvailableForDimensions([width, height]); + const [selected] = available.find(([name, size]) => size <= preferredSize); + return selected; +} + function readFileMD5(filePath) { return new Promise((resolve, reject) => { const md5 = createHash('md5'); @@ -302,7 +326,7 @@ export async function clearThumbs(mediaPath, { console.error(file); } fileIssue(); - return; + return {success: false}; } logInfo`Clearing out ${thumbFiles.length} thumbs.`; @@ -327,6 +351,7 @@ export async function clearThumbs(mediaPath, { console.error(file); } logError`Check for permission errors?`; + return {success: false}; } else { logInfo`Successfully deleted all ${thumbFiles.length} thumbnail files!`; } @@ -355,6 +380,8 @@ export async function clearThumbs(mediaPath, { logWarn`Failed to remove cache file. Check its permissions?`; } } + + return {success: true}; } export default async function genThumbs(mediaPath, { -- cgit 1.3.0-6-gf8a5 From 2d1eaf5ea4c46527406df527f1b4d2fc36d8566e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 28 Aug 2023 14:45:38 -0300 Subject: thumbs: fix how magickThreads controlls queue --- src/gen-thumbs.js | 54 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 21 deletions(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index f6a8eaaf..51b2c72d 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -263,6 +263,8 @@ async function getSpawnMagick(tool) { return [`${description} (${version})`, fn]; } +// Note: This returns an array of no-argument functions, suitable for passing +// to queue(). function generateImageThumbnails({ filePath, dimensions, @@ -286,10 +288,10 @@ function generateImageThumbnails({ output(name), ]); - return Promise.all( + return ( getThumbnailsAvailableForDimensions(dimensions) .map(([name]) => [name, thumbnailSpec[name]]) - .map(([name, details]) => + .map(([name, details]) => () => promisifyProcess(convert('.' + name, details), false))); } @@ -527,35 +529,45 @@ export default async function genThumbs(mediaPath, { } const failed = []; - const succeeded = []; + const writeMessageFn = () => `Writing image thumbnails. [failed: ${failed.length}]`; + const generateCalls = + entriesToGenerate.flatMap(([filePath, md5]) => + generateImageThumbnails({ + filePath: path.join(mediaPath, filePath), + dimensions: imageToDimensions[filePath], + spawnConvert, + }).map(call => async () => { + try { + await call(); + } catch (error) { + failed.push([filePath, error]); + } + })); + await progressPromiseAll(writeMessageFn, - queue( - entriesToGenerate.map(([filePath, md5]) => () => - generateImageThumbnails({ - filePath: path.join(mediaPath, filePath), - dimensions: imageToDimensions[filePath], - spawnConvert, - }).then( - () => { - updatedCache[filePath] = [md5, ...imageToDimensions[filePath]]; - succeeded.push(filePath); - }, - error => { - failed.push([filePath, error]); - })), - magickThreads)); + queue(generateCalls, magickThreads)); + + // Sort by file path. + failed.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0); + + const failedFilePaths = new Set(failed.map(([filePath]) => filePath)); + + for (const [filePath, md5] of entriesToGenerate) { + if (failedFilePaths.has(filePath)) continue; + updatedCache[filePath] = [md5, ...imageToDimensions[filePath]]; + } if (empty(failed)) { logInfo`Generated all (updated) thumbnails successfully!`; } else { for (const [path, error] of failed) { - logError`Thumbnails failed to generate for ${path} - ${error}`; + logError`Thumbnail failed to generate for ${path} - ${error}`; } - logWarn`Result is incomplete - the above ${failed.length} thumbnails should be checked for errors.`; - logWarn`${succeeded.length} successfully generated images won't be regenerated next run, though!`; + logWarn`Result is incomplete - the above thumbnails should be checked for errors.`; + logWarn`Successfully generated images won't be regenerated next run, though!`; } try { -- cgit 1.3.0-6-gf8a5 From 7069268db096ab0aa7a8839a3594efb2d1be8f86 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 4 Sep 2023 20:46:58 -0300 Subject: thumbs: new check-has-thumbs util, others throw for missing info --- src/gen-thumbs.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index 51b2c72d..741cdff3 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -168,11 +168,23 @@ getThumbnailsAvailableForDimensions.all = .map(([name, {size}]) => [name, size]) .sort((a, b) => b[1] - a[1]); +export function checkIfImagePathHasCachedThumbnails(imagePath, cache) { + // Generic utility for checking if the thumbnail cache includes any info for + // the provided image path, so that the other functions don't hard-code the + // cache format. + + return !!cache[imagePath]; +} + export function getDimensionsOfImagePath(imagePath, cache) { // This function is really generic. It takes the gen-thumbs image cache and // returns the dimensions in that cache, so that other functions don't need // to hard-code the cache format. + if (!cache[imagePath]) { + throw new Error(`Expected imagePath to be included in cache, got ${imagePath}`); + } + const [width, height] = cache[imagePath].slice(1); return [width, height]; } @@ -185,6 +197,10 @@ export function getThumbnailEqualOrSmaller(preferred, imagePath, cache) { // one which is being thumbnail-ified, this just returns the name of the // selected thumbnail size. + if (!cache[imagePath]) { + throw new Error(`Expected imagePath to be included in cache, got ${imagePath}`); + } + const {size: preferredSize} = thumbnailSpec[preferred]; const [width, height] = getDimensionsOfImagePath(imagePath, cache); const available = getThumbnailsAvailableForDimensions([width, height]); -- cgit 1.3.0-6-gf8a5 From eb00f2993a1aaaba171ad6c918656552f80bb748 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 7 Sep 2023 12:38:34 -0300 Subject: data: import Thing.common utilities directly Also rename 'color' (from #cli) to 'colors'. --- src/gen-thumbs.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index 741cdff3..fafd17f6 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -94,7 +94,7 @@ import * as path from 'node:path'; import dimensionsOf from 'image-size'; import { - color, + colors, fileIssue, logError, logInfo, @@ -662,14 +662,14 @@ export async function verifyImagePaths(mediaPath, {urls, wikiData}) { if (!empty(missing)) { logWarn`** Some image files are missing! (${missing.length + ' files'}) **`; for (const file of missing) { - console.warn(color.yellow(` - `) + file); + console.warn(colors.yellow(` - `) + file); } } if (!empty(misplaced)) { logWarn`** Some image files are misplaced! (${misplaced.length + ' files'}) **`; for (const file of misplaced) { - console.warn(color.yellow(` - `) + file); + console.warn(colors.yellow(` - `) + file); } } } -- cgit 1.3.0-6-gf8a5 From bbccaf51222cb4bed73466164496f5bc1030292c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 7 Sep 2023 17:30:54 -0300 Subject: data: roll paired "byRef" and "dynamic" properties into one --- src/gen-thumbs.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index fafd17f6..4977ade7 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -93,6 +93,9 @@ import * as path from 'node:path'; import dimensionsOf from 'image-size'; +import {delay, empty, queue} from '#sugar'; +import {CacheableObject} from '#things'; + import { colors, fileIssue, @@ -110,8 +113,6 @@ import { traverse, } from '#node-utils'; -import {delay, empty, queue} from '#sugar'; - export const defaultMagickThreads = 8; export function getThumbnailsAvailableForDimensions([width, height]) { @@ -608,8 +609,8 @@ export function getExpectedImagePaths(mediaPath, {urls, wikiData}) { wikiData.albumData .flatMap(album => [ album.hasCoverArt && fromRoot.to('media.albumCover', album.directory, album.coverArtFileExtension), - !empty(album.bannerArtistContribsByRef) && fromRoot.to('media.albumBanner', album.directory, album.bannerFileExtension), - !empty(album.wallpaperArtistContribsByRef) && fromRoot.to('media.albumWallpaper', album.directory, album.wallpaperFileExtension), + !empty(CacheableObject.getUpdateValue(album, 'bannerArtistContribs')) && fromRoot.to('media.albumBanner', album.directory, album.bannerFileExtension), + !empty(CacheableObject.getUpdateValue(album, 'wallpaperArtistContribs')) && fromRoot.to('media.albumWallpaper', album.directory, album.wallpaperFileExtension), ]) .filter(Boolean), -- cgit 1.3.0-6-gf8a5 From 78115f0be17ee405d3711204aaa53e0597a29826 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 11 Sep 2023 14:34:12 -0300 Subject: thumbs: read win32-style path from cache --- src/gen-thumbs.js | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index 741cdff3..a5b550ad 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -168,28 +168,45 @@ getThumbnailsAvailableForDimensions.all = .map(([name, {size}]) => [name, size]) .sort((a, b) => b[1] - a[1]); -export function checkIfImagePathHasCachedThumbnails(imagePath, cache) { +function getCacheEntryForMediaPath(mediaPath, cache) { + // Gets the cache entry for the provided image path, which should always be + // a forward-slashes path (i.e. suitable for display online). Since the cache + // file may have forward or back-slashes, this checks both. + + const entryFromMediaPath = cache[mediaPath]; + if (entryFromMediaPath) return entryFromMediaPath; + + const winPath = mediaPath.split(path.posix.sep).join(path.win32.sep); + const entryFromWinPath = cache[winPath]; + if (entryFromWinPath) return entryFromWinPath; + + return null; +} + +export function checkIfImagePathHasCachedThumbnails(mediaPath, cache) { // Generic utility for checking if the thumbnail cache includes any info for // the provided image path, so that the other functions don't hard-code the // cache format. - return !!cache[imagePath]; + return !!getCacheEntryForMediaPath(mediaPath, cache); } -export function getDimensionsOfImagePath(imagePath, cache) { +export function getDimensionsOfImagePath(mediaPath, cache) { // This function is really generic. It takes the gen-thumbs image cache and // returns the dimensions in that cache, so that other functions don't need // to hard-code the cache format. - if (!cache[imagePath]) { - throw new Error(`Expected imagePath to be included in cache, got ${imagePath}`); + const cacheEntry = getCacheEntryForMediaPath(mediaPath, cache); + + if (!cacheEntry) { + throw new Error(`Expected mediaPath to be included in cache, got ${mediaPath}`); } - const [width, height] = cache[imagePath].slice(1); + const [width, height] = cacheEntry.slice(1); return [width, height]; } -export function getThumbnailEqualOrSmaller(preferred, imagePath, cache) { +export function getThumbnailEqualOrSmaller(preferred, mediaPath, cache) { // This function is totally exclusive to page generation. It's a shorthand // for accessing dimensions from the thumbnail cache, calculating all the // thumbnails available, and selecting the one which is equal to or smaller @@ -197,12 +214,12 @@ export function getThumbnailEqualOrSmaller(preferred, imagePath, cache) { // one which is being thumbnail-ified, this just returns the name of the // selected thumbnail size. - if (!cache[imagePath]) { - throw new Error(`Expected imagePath to be included in cache, got ${imagePath}`); + if (!getCacheEntryForMediaPath(mediaPath, cache)) { + throw new Error(`Expected mediaPath to be included in cache, got ${mediaPath}`); } const {size: preferredSize} = thumbnailSpec[preferred]; - const [width, height] = getDimensionsOfImagePath(imagePath, cache); + const [width, height] = getDimensionsOfImagePath(mediaPath, cache); const available = getThumbnailsAvailableForDimensions([width, height]); const [selected] = available.find(([name, size]) => size <= preferredSize); return selected; -- cgit 1.3.0-6-gf8a5 From b19e165dc8ba13cd0e2d1862e645d34d86142566 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 11 Sep 2023 15:09:19 -0300 Subject: thumbs, infra: expose list of missing image paths --- src/gen-thumbs.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index a5b550ad..b7c192c3 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -689,6 +689,8 @@ export async function verifyImagePaths(mediaPath, {urls, wikiData}) { console.warn(color.yellow(` - `) + file); } } + + return {missing, misplaced}; } // Recursively traverses the provided (extant) media path, filtering so only -- cgit 1.3.0-6-gf8a5 From 0bb9482518badd10b6e2c3e8e2ba99367b6f6fd1 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 12 Sep 2023 15:14:13 -0300 Subject: thumbs: return correct function signature, yes, yes --- src/gen-thumbs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index b7c192c3..18d1964d 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -673,7 +673,7 @@ export async function verifyImagePaths(mediaPath, {urls, wikiData}) { if (empty(missing) && empty(misplaced)) { logInfo`All image paths are good - nice! None are missing or misplaced.`; - return; + return {missing, misplaced}; } if (!empty(missing)) { -- cgit 1.3.0-6-gf8a5 From 0e613cf94a3fade7050fc2e50e8bfbab8d532ad2 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 19 Sep 2023 13:21:08 -0300 Subject: thumbs: probably fix using wrong convert command?? --- src/gen-thumbs.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index 18d1964d..65f0a4da 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -250,7 +250,11 @@ async function getImageMagickVersion(binary) { allData += data.toString(); }); - await promisifyProcess(proc, false); + try { + await promisifyProcess(proc, false); + } catch (error) { + return null; + } if (!allData.match(/ImageMagick/i)) { return null; -- cgit 1.3.0-6-gf8a5 From 9745cf89d808bc2e4698af346ddbc53e1ae08a2c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 19 Sep 2023 13:25:49 -0300 Subject: SO A BIT EMBARRASSING --- src/gen-thumbs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/gen-thumbs.js') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index 65f0a4da..34eed9c1 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -286,7 +286,7 @@ async function getSpawnMagick(tool) { } if (fn === null && await commandExists('magick')) { - version = await getImageMagickVersion(fn); + version = await getImageMagickVersion('magick'); if (version !== null) { fn = (args) => spawn('magick', [tool, ...args]); description = `magick ${tool}`; -- cgit 1.3.0-6-gf8a5