Skip to content

Commit c99b305

Browse files
committed
Add TIFF image conversion support to mg-assetscraper-db
TIFF is not on Ghost's media allow-list, so previously any .tif/.tiff URLs were downloaded, identified as image/tiff, then silently dropped because getFolderForMimeType returned null. Add image/tiff to needsConverting so the existing sharp-based path re-encodes them to lossless WebP, same as AVIF. The resulting file gets stored under /content/images and the URL is rewritten in content.
1 parent 0e2e15f commit c99b305

3 files changed

Lines changed: 29 additions & 3 deletions

File tree

packages/mg-assetscraper-db/src/lib/AssetScraper.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,8 @@ export default class AssetScraper {
725725
'image/svg+xml': 'svg',
726726
'image/avif': 'avif',
727727
'image/heif': 'heif',
728-
'image/heic': 'heic'
728+
'image/heic': 'heic',
729+
'image/tiff': 'tif'
729730
};
730731
extension = mimeToExtMap[mimeType] || 'jpg';
731732
}

packages/mg-assetscraper-db/src/lib/utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ const knownMediaTypes = ['video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', '
1010
const knownFileTypes = ['application/pdf', 'application/json', 'application/ld+json', 'application/vnd.oasis.opendocument.presentation', 'application/vnd.oasis.opendocument.spreadsheet', 'application/vnd.oasis.opendocument.text', 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'application/rtf', 'text/plain', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/xml', 'application/atom+xml'
1111
];
1212

13-
export const needsConverting = ['image/avif', 'image/heif', 'image/heic'];
13+
export const needsConverting = ['image/avif', 'image/heif', 'image/heic', 'image/tiff'];
1414

1515
/**
16-
* Convert HEIC/HEIF/AVIF images to supported formats (JPEG or WebP)
16+
* Convert HEIC/HEIF/AVIF/TIFF images to supported formats (JPEG or WebP).
17+
* Ghost's media allow-list does not include TIFF, so we re-encode to WebP via
18+
* sharp (libvips) the same way we do for AVIF.
1719
* @param buffer - The image buffer to convert
1820
* @param fileMime - The mime type of the image
1921
* @returns Object with converted buffer, extension, and mime type

packages/mg-assetscraper-db/src/test/AssetScraper.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,19 @@ describe('Asset Scraper', () => {
2929
}
3030
let avifImageBuffer: Buffer;
3131
let heicImageBuffer: Buffer;
32+
let tiffImageBuffer: Buffer;
3233
let mp4VideoBuffer: Buffer;
3334
let mp3AudioBuffer: Buffer;
3435

3536
before(async () => {
3637
jpgImageBuffer = await readFile(join(fixturesPath, '/image.jpg'));
3738
avifImageBuffer = await readFile(join(fixturesPath, '/image.avif'));
3839
heicImageBuffer = await readFile(join(fixturesPath, '/image.heic'));
40+
// Generate a small TIFF fixture at runtime to avoid checking in a binary file
41+
const sharp = (await import('sharp')).default;
42+
tiffImageBuffer = await sharp({
43+
create: {width: 8, height: 8, channels: 3, background: {r: 255, g: 0, b: 0}}
44+
}).tiff().toBuffer();
3945
mp4VideoBuffer = await readFile(join(fixturesPath, '/video.mp4'));
4046
mp3AudioBuffer = await readFile(join(fixturesPath, '/audio.mp3'));
4147
});
@@ -559,6 +565,23 @@ describe('Asset Scraper', () => {
559565
assert.equal(responseData.extension, '.jpg');
560566
});
561567

568+
it('extractFileDataFromResponse tiff to webp', async () => {
569+
const requestMock = nock('https://example.com')
570+
.get('/assets/2025/03/photo.tif')
571+
.reply(200, tiffImageBuffer);
572+
573+
const assetScraper = await createScraper({});
574+
575+
const response = await assetScraper.getRemoteMedia('https://example.com/assets/2025/03/photo.tif');
576+
const responseData: any = await assetScraper.extractFileDataFromResponse('https://example.com/assets/2025/03/photo.tif', response);
577+
578+
assert.ok(requestMock.isDone());
579+
assert.equal(responseData.fileBuffer.constructor.name, 'Buffer');
580+
assert.equal(responseData.fileName, 'photo.webp');
581+
assert.equal(responseData.fileMime, 'image/webp');
582+
assert.equal(responseData.extension, '.webp');
583+
});
584+
562585
it('extractFileDataFromResponse falls back to content-type header and URL extension', async () => {
563586
const assetScraper = await createScraper({});
564587

0 commit comments

Comments
 (0)