mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
Moved getLocalSize()
from mobiledoc to image-size lib
no issue - `getLocalSize()` is useful outside of the mobiledoc populate-image-sizes function - expanded `ImageSize` class with new methods - `getOriginalImageSizeFromStoragePath()` - takes the "original" image extraction and test from `getLocalSize()` and makes it more generally available - `getImageSizeFromStorageUrl()` - takes the path extraction from `getLocalSize()` to make image sizes from local urls more generally available - `getOriginalImageSizeFromStorageUrl()` - URL version of the new `getOriginalImageSizeFromStoragePath()` method
This commit is contained in:
parent
14cae4b154
commit
7070572e4f
2 changed files with 57 additions and 48 deletions
|
@ -2,6 +2,7 @@ const debug = require('ghost-ignition').debug('utils:image-size');
|
|||
const sizeOf = require('image-size');
|
||||
const probeSizeOf = require('probe-image-size');
|
||||
const url = require('url');
|
||||
const path = require('path');
|
||||
const Promise = require('bluebird');
|
||||
const _ = require('lodash');
|
||||
const errors = require('@tryghost/errors');
|
||||
|
@ -38,14 +39,14 @@ class ImageSize {
|
|||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const dimensions = sizeOf(buffer);
|
||||
|
||||
|
||||
// CASE: `.ico` files might have multiple images and therefore multiple sizes.
|
||||
// We return the largest size found (image-size default is the first size found)
|
||||
if (dimensions.images) {
|
||||
dimensions.width = _.maxBy(dimensions.images, img => img.width).width;
|
||||
dimensions.height = _.maxBy(dimensions.images, img => img.height).height;
|
||||
}
|
||||
|
||||
|
||||
return resolve(dimensions);
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
|
@ -65,7 +66,7 @@ class ImageSize {
|
|||
context: imageUrl
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
return probeSizeOf(imageUrl, this.REQUEST_OPTIONS);
|
||||
}
|
||||
|
||||
|
@ -82,23 +83,23 @@ class ImageSize {
|
|||
_imageSizeFromUrl(imageUrl) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let parsedUrl;
|
||||
|
||||
|
||||
try {
|
||||
parsedUrl = url.parse(imageUrl);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
|
||||
// check if we got an url without any protocol
|
||||
if (!parsedUrl.protocol) {
|
||||
// CASE: our gravatar URLs start with '//' and we need to add 'http:'
|
||||
// to make the request work
|
||||
imageUrl = 'http:' + imageUrl;
|
||||
}
|
||||
|
||||
|
||||
const extensionMatch = imageUrl.match(/(?:\.)([a-zA-Z]{3,4})(\?|$)/) || [];
|
||||
const extension = (extensionMatch[1] || '').toLowerCase();
|
||||
|
||||
|
||||
if (FETCH_ONLY_FORMATS.includes(extension)) {
|
||||
return resolve(this._fetchImageSizeFromUrl(imageUrl));
|
||||
} else {
|
||||
|
@ -123,7 +124,7 @@ class ImageSize {
|
|||
// In case the image is not stored locally and is missing the protocol (like //www.gravatar.com/andsoon),
|
||||
// we add the protocol and use urlFor() to get the absolute URL.
|
||||
// If the request fails or image-size is not able to read the file, we reject with error.
|
||||
|
||||
|
||||
/**
|
||||
* @description read image dimensions from URL
|
||||
* @param {string} imagePath as URL
|
||||
|
@ -134,17 +135,17 @@ class ImageSize {
|
|||
// don't make a request for a locally stored image
|
||||
return this.getImageSizeFromStoragePath(imagePath);
|
||||
}
|
||||
|
||||
|
||||
// CASE: pre 1.0 users were able to use an asset path for their blog logo
|
||||
if (imagePath.match(/^\/assets/)) {
|
||||
imagePath = this.urlUtils.urlJoin(this.urlUtils.urlFor('home', true), this.urlUtils.getSubdir(), '/', imagePath);
|
||||
}
|
||||
|
||||
|
||||
debug('requested imagePath:', imagePath);
|
||||
|
||||
|
||||
return this._imageSizeFromUrl(imagePath).then((dimensions) => {
|
||||
debug('Image fetched (URL):', imagePath);
|
||||
|
||||
|
||||
return {
|
||||
url: imagePath,
|
||||
width: dimensions.width,
|
||||
|
@ -175,7 +176,7 @@ class ImageSize {
|
|||
if (errors.utils.isIgnitionError(err)) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
|
||||
return Promise.reject(new errors.InternalServerError({
|
||||
message: 'Unknown Request error.',
|
||||
code: 'IMAGE_SIZE_URL',
|
||||
|
@ -205,12 +206,12 @@ class ImageSize {
|
|||
*/
|
||||
getImageSizeFromStoragePath(imagePath) {
|
||||
let filePath;
|
||||
|
||||
|
||||
imagePath = this.urlUtils.urlFor('image', {image: imagePath}, true);
|
||||
|
||||
|
||||
// get the storage readable filePath
|
||||
filePath = this.storageUtils.getLocalFileStoragePath(imagePath);
|
||||
|
||||
|
||||
return this.storage.getStorage()
|
||||
.read({path: filePath})
|
||||
.then((buf) => {
|
||||
|
@ -239,7 +240,7 @@ class ImageSize {
|
|||
if (errors.utils.isIgnitionError(err)) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
|
||||
|
||||
return Promise.reject(new errors.InternalServerError({
|
||||
message: err.message,
|
||||
code: 'IMAGE_SIZE_STORAGE',
|
||||
|
@ -253,22 +254,53 @@ class ImageSize {
|
|||
});
|
||||
}
|
||||
|
||||
async getOriginalImageSizeFromStoragePath(imagePath) {
|
||||
const {dir, name, ext} = path.parse(imagePath);
|
||||
const [imageNameMatched, imageName, imageNumber] = name.match(/^(.+?)(-\d+)?$/) || [null];
|
||||
|
||||
if (!imageNameMatched) {
|
||||
return this.getImageSizeFromStoragePath(imagePath);
|
||||
}
|
||||
|
||||
const originalImagePath = path.join(dir, `${imageName}_o${imageNumber || ''}${ext}`);
|
||||
const originalImageExists = await this.storage.getStorage().exists(originalImagePath);
|
||||
|
||||
return this.getImageSizeFromStoragePath(originalImageExists ? originalImagePath : imagePath);
|
||||
}
|
||||
|
||||
_getPathFromUrl(imageUrl) {
|
||||
// local storage adapter's .exists() expects image paths without any prefixes
|
||||
const subdirRegex = new RegExp(`^${this.urlUtils.getSubdir()}`);
|
||||
const contentRegex = new RegExp(`^/${this.urlUtils.STATIC_IMAGE_URL_PREFIX}`);
|
||||
const storagePath = imageUrl.replace(subdirRegex, '').replace(contentRegex, '');
|
||||
|
||||
return storagePath;
|
||||
}
|
||||
|
||||
getImageSizeFromStorageUrl(imageUrl) {
|
||||
return this.getImageSizeFromStoragePath(this._getPathFromUrl(imageUrl));
|
||||
}
|
||||
|
||||
getOriginalImageSizeFromStorageUrl(imageUrl) {
|
||||
return this.getOriginalImageSizeFromStoragePath(this._getPathFromUrl(imageUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* Supported formats of https://github.com/image-size/image-size:
|
||||
* BMP, GIF, JPEG, PNG, PSD, TIFF, WebP, SVG, ICO
|
||||
* Get dimensions for a file from its real file storage path
|
||||
* Always returns {object} getImageDimensions
|
||||
* @param {string} path
|
||||
* @param {string} imagePath
|
||||
* @returns {Promise<Object>} getImageDimensions
|
||||
* @description Takes a file path and returns width and height.
|
||||
*/
|
||||
getImageSizeFromPath(path) {
|
||||
getImageSizeFromPath(imagePath) {
|
||||
return new Promise(function getSize(resolve, reject) {
|
||||
let dimensions;
|
||||
|
||||
|
||||
try {
|
||||
dimensions = sizeOf(path);
|
||||
|
||||
dimensions = sizeOf(imagePath);
|
||||
|
||||
if (dimensions.images) {
|
||||
dimensions.width = _.maxBy(dimensions.images, (w) => {
|
||||
return w.width;
|
||||
|
@ -277,7 +309,7 @@ class ImageSize {
|
|||
return h.height;
|
||||
}).height;
|
||||
}
|
||||
|
||||
|
||||
return resolve({
|
||||
width: dimensions.width,
|
||||
height: dimensions.height
|
||||
|
@ -285,7 +317,7 @@ class ImageSize {
|
|||
} catch (err) {
|
||||
return reject(new errors.ValidationError({
|
||||
message: this.i18n.t('errors.utils.images.invalidDimensions', {
|
||||
file: path,
|
||||
file: imagePath,
|
||||
error: err.message
|
||||
})
|
||||
}));
|
||||
|
|
|
@ -94,8 +94,6 @@ module.exports = {
|
|||
// do not require image-size until it's requested to avoid circular dependencies
|
||||
// shared/url-utils > server/lib/mobiledoc > server/lib/image/image-size > server/adapters/storage/utils
|
||||
const {imageSize} = require('./image');
|
||||
const urlUtils = require('../../shared/url-utils');
|
||||
const storageInstance = storage.getStorage();
|
||||
|
||||
async function getUnsplashSize(url) {
|
||||
const parsedUrl = new URL(url);
|
||||
|
@ -107,27 +105,6 @@ module.exports = {
|
|||
return await imageSize.getImageSizeFromUrl(parsedUrl.href);
|
||||
}
|
||||
|
||||
async function getLocalSize(url) {
|
||||
// local storage adapter's .exists() expects image paths without any prefixes
|
||||
const subdirRegex = new RegExp(`^${urlUtils.getSubdir()}`);
|
||||
const contentRegex = new RegExp(`^/${urlUtils.STATIC_IMAGE_URL_PREFIX}`);
|
||||
const storagePath = url.replace(subdirRegex, '').replace(contentRegex, '');
|
||||
|
||||
const {dir, name, ext} = path.parse(storagePath);
|
||||
const [imageNameMatched, imageName, imageNumber] = name.match(/^(.+?)(-\d+)?$/) || [null];
|
||||
|
||||
if (!imageNameMatched || !(await storageInstance.exists(storagePath))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get the original/unoptimized image if it exists as that will have
|
||||
// the maximum dimensions that srcset/handle-image-sizes can use
|
||||
const originalImagePath = path.join(dir, `${imageName}_o${imageNumber || ''}${ext}`);
|
||||
const imagePath = await storageInstance.exists(originalImagePath) ? originalImagePath : storagePath;
|
||||
|
||||
return await imageSize.getImageSizeFromStoragePath(imagePath);
|
||||
}
|
||||
|
||||
const mobiledoc = JSON.parse(mobiledocJson);
|
||||
|
||||
const sizePromises = mobiledoc.cards.map(async (card) => {
|
||||
|
@ -140,7 +117,7 @@ module.exports = {
|
|||
|
||||
const isUnsplash = payload.src.match(/images\.unsplash\.com/);
|
||||
try {
|
||||
const size = isUnsplash ? await getUnsplashSize(payload.src) : await getLocalSize(payload.src);
|
||||
const size = isUnsplash ? await getUnsplashSize(payload.src) : await imageSize.getOriginalImageSizeFromStorageUrl(payload.src);
|
||||
|
||||
if (size && size.width && size.height) {
|
||||
payload.width = size.width;
|
||||
|
|
Loading…
Add table
Reference in a new issue