Skip to content

Commit 6d18c6f

Browse files
committed
Add Media Type to metadata response #4492
1 parent 2291c0b commit 6d18c6f

File tree

8 files changed

+93
-5
lines changed

8 files changed

+93
-5
lines changed

docs/src/content/docs/api-input.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Dimensions in the response will respect the `page` and `pages` properties of the
1818
A `Promise` is returned when `callback` is not provided.
1919

2020
- `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff`
21+
- `mediaType`: Media Type (MIME Type) e.g. `image/jpeg`, `image/png`, `image/svg+xml`, `image/avif`
2122
- `size`: Total size of image in bytes, for Stream and Buffer input only
2223
- `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
2324
- `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)

docs/src/content/docs/changelog/v0.35.0.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,7 @@ slug: changelog/v0.35.0
4343
* Ensure HEIF primary item is used as default page/frame.
4444
[#4487](https://github.com/lovell/sharp/issues/4487)
4545

46+
* Add image Media Type (MIME Type) to metadata response.
47+
[#4492](https://github.com/lovell/sharp/issues/4492)
48+
4649
* Add WebP `exact` option for control over transparent pixel colour values.

lib/input.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,7 @@ function _isStreamInput () {
567567
* A `Promise` is returned when `callback` is not provided.
568568
*
569569
* - `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff`
570+
* - `mediaType`: Media Type (MIME Type) e.g. `image/jpeg`, `image/png`, `image/svg+xml`, `image/avif`
570571
* - `size`: Total size of image in bytes, for Stream and Buffer input only
571572
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
572573
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)

src/metadata.cc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,50 @@ class MetadataWorker : public Napi::AsyncWorker {
151151
}
152152
// PNG comments
153153
vips_image_map(image.get_image(), readPNGComment, &baton->comments);
154+
// Media type
155+
std::string mediaType;
156+
switch (imageType) {
157+
case sharp::ImageType::JPEG:
158+
case sharp::ImageType::PNG:
159+
case sharp::ImageType::WEBP:
160+
case sharp::ImageType::JP2:
161+
case sharp::ImageType::TIFF:
162+
case sharp::ImageType::GIF:
163+
case sharp::ImageType::FITS:
164+
case sharp::ImageType::JXL:
165+
baton->mediaType = "image/" + baton->format;
166+
break;
167+
case sharp::ImageType::SVG:
168+
baton->mediaType = "image/svg+xml";
169+
break;
170+
case sharp::ImageType::HEIF:
171+
if (baton->compression == "av1") {
172+
baton->mediaType = "image/avif";
173+
} else if (baton->compression == "hevc") {
174+
baton->mediaType = "image/heic";
175+
}
176+
break;
177+
case sharp::ImageType::PDF:
178+
baton->mediaType = "application/pdf";
179+
break;
180+
case sharp::ImageType::OPENSLIDE:
181+
baton->mediaType = "image/tiff";
182+
break;
183+
case sharp::ImageType::PPM:
184+
baton->mediaType = "image/x-portable-pixmap";
185+
break;
186+
case sharp::ImageType::EXR:
187+
baton->mediaType = "image/x-exr";
188+
break;
189+
case sharp::ImageType::RAD:
190+
baton->mediaType = "image/vnd.radiance";
191+
break;
192+
case sharp::ImageType::UHDR:
193+
baton->mediaType = "image/jpeg";
194+
break;
195+
default:
196+
break;
197+
}
154198
}
155199

156200
// Clean up
@@ -172,6 +216,9 @@ class MetadataWorker : public Napi::AsyncWorker {
172216
if (baton->err.empty()) {
173217
Napi::Object info = Napi::Object::New(env);
174218
info.Set("format", baton->format);
219+
if (!baton->mediaType.empty()) {
220+
info.Set("mediaType", baton->mediaType);
221+
}
175222
if (baton->input->bufferLength > 0) {
176223
info.Set("size", baton->input->bufferLength);
177224
}

src/metadata.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct MetadataBaton {
1919
sharp::InputDescriptor *input;
2020
// Output
2121
std::string format;
22+
std::string mediaType;
2223
int width;
2324
int height;
2425
std::string space;

test/unit/avif.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ describe('AVIF', () => {
3636
density: 72,
3737
depth: 'uchar',
3838
format: 'jpeg',
39+
mediaType: 'image/jpeg',
3940
hasAlpha: false,
4041
hasProfile: false,
4142
// 32 / (2048 / 858) = 13.40625
@@ -64,6 +65,7 @@ describe('AVIF', () => {
6465
compression: 'av1',
6566
depth: 'uchar',
6667
format: 'heif',
68+
mediaType: 'image/avif',
6769
hasAlpha: false,
6870
hasProfile: false,
6971
height: 26,
@@ -93,6 +95,7 @@ describe('AVIF', () => {
9395
compression: 'av1',
9496
depth: 'uchar',
9597
format: 'heif',
98+
mediaType: 'image/avif',
9699
hasAlpha: false,
97100
hasProfile: false,
98101
height: 24,
@@ -119,6 +122,7 @@ describe('AVIF', () => {
119122
compression: 'av1',
120123
depth: 'uchar',
121124
format: 'heif',
125+
mediaType: 'image/avif',
122126
hasAlpha: false,
123127
hasProfile: false,
124128
height: 13,
@@ -148,6 +152,7 @@ describe('AVIF', () => {
148152
compression: 'av1',
149153
depth: 'uchar',
150154
format: 'heif',
155+
mediaType: 'image/avif',
151156
hasAlpha: true,
152157
hasProfile: false,
153158
height: 300,
@@ -178,6 +183,7 @@ describe('AVIF', () => {
178183
compression: 'av1',
179184
depth: 'uchar',
180185
format: 'heif',
186+
mediaType: 'image/avif',
181187
hasAlpha: false,
182188
hasProfile: false,
183189
height: 26,
@@ -236,6 +242,7 @@ describe('AVIF', () => {
236242
void exif;
237243
assert.deepStrictEqual(metadata, {
238244
format: 'heif',
245+
mediaType: 'image/avif',
239246
width: 4096,
240247
height: 800,
241248
space: 'srgb',
@@ -259,6 +266,7 @@ describe('AVIF', () => {
259266
const { size, ...pngMetadata } = await sharp(data).metadata();
260267
assert.deepStrictEqual(pngMetadata, {
261268
format: 'png',
269+
mediaType: 'image/png',
262270
width: 4096,
263271
height: 800,
264272
space: 'srgb',

test/unit/metadata.js

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describe('Image metadata', () => {
1919
sharp(fixtures.inputJpg).metadata((err, metadata) => {
2020
if (err) throw err;
2121
assert.strictEqual('jpeg', metadata.format);
22+
assert.strictEqual('image/jpeg', metadata.mediaType);
2223
assert.strictEqual('undefined', typeof metadata.size);
2324
assert.strictEqual(2725, metadata.width);
2425
assert.strictEqual(2225, metadata.height);
@@ -41,6 +42,7 @@ describe('Image metadata', () => {
4142
sharp(fixtures.inputJpgWithExif).metadata((err, metadata) => {
4243
if (err) throw err;
4344
assert.strictEqual('jpeg', metadata.format);
45+
assert.strictEqual('image/jpeg', metadata.mediaType);
4446
assert.strictEqual('undefined', typeof metadata.size);
4547
assert.strictEqual(450, metadata.width);
4648
assert.strictEqual(600, metadata.height);
@@ -66,6 +68,7 @@ describe('Image metadata', () => {
6668
const profile = icc.parse(metadata.icc);
6769
assert.strictEqual('object', typeof profile);
6870
assert.strictEqual('Generic RGB Profile', profile.description);
71+
assert.strictEqual('image/jpeg', metadata.mediaType);
6972
done();
7073
});
7174
});
@@ -92,6 +95,7 @@ describe('Image metadata', () => {
9295
sharp(fixtures.inputTiff).metadata((err, metadata) => {
9396
if (err) throw err;
9497
assert.strictEqual('tiff', metadata.format);
98+
assert.strictEqual('image/tiff', metadata.mediaType);
9599
assert.strictEqual('undefined', typeof metadata.size);
96100
assert.strictEqual(2464, metadata.width);
97101
assert.strictEqual(3248, metadata.height);
@@ -111,6 +115,7 @@ describe('Image metadata', () => {
111115
assert.strictEqual('undefined', typeof metadata.xmp);
112116
assert.strictEqual('undefined', typeof metadata.xmpAsString);
113117
assert.strictEqual('inch', metadata.resolutionUnit);
118+
assert.strictEqual('image/tiff', metadata.mediaType);
114119
done();
115120
});
116121
});
@@ -119,6 +124,7 @@ describe('Image metadata', () => {
119124
sharp(fixtures.inputTiffMultipage).metadata((err, metadata) => {
120125
if (err) throw err;
121126
assert.strictEqual('tiff', metadata.format);
127+
assert.strictEqual('image/tiff', metadata.mediaType);
122128
assert.strictEqual('undefined', typeof metadata.size);
123129
assert.strictEqual(2464, metadata.width);
124130
assert.strictEqual(3248, metadata.height);
@@ -142,6 +148,7 @@ describe('Image metadata', () => {
142148
sharp(fixtures.inputPng).metadata((err, metadata) => {
143149
if (err) throw err;
144150
assert.strictEqual('png', metadata.format);
151+
assert.strictEqual('image/png', metadata.mediaType);
145152
assert.strictEqual('undefined', typeof metadata.size);
146153
assert.strictEqual(2809, metadata.width);
147154
assert.strictEqual(2074, metadata.height);
@@ -166,6 +173,7 @@ describe('Image metadata', () => {
166173
sharp(fixtures.inputPngTestJoinChannel).metadata((err, metadata) => {
167174
if (err) throw err;
168175
assert.strictEqual('png', metadata.format);
176+
assert.strictEqual('image/png', metadata.mediaType);
169177
assert.strictEqual('undefined', typeof metadata.size);
170178
assert.strictEqual(320, metadata.width);
171179
assert.strictEqual(240, metadata.height);
@@ -191,6 +199,7 @@ describe('Image metadata', () => {
191199
sharp(fixtures.inputPngWithTransparency).metadata((err, metadata) => {
192200
if (err) throw err;
193201
assert.strictEqual('png', metadata.format);
202+
assert.strictEqual('image/png', metadata.mediaType);
194203
assert.strictEqual('undefined', typeof metadata.size);
195204
assert.strictEqual(2048, metadata.width);
196205
assert.strictEqual(1536, metadata.height);
@@ -225,6 +234,7 @@ describe('Image metadata', () => {
225234
height: 32,
226235
isPalette: false,
227236
isProgressive: false,
237+
mediaType: 'image/png',
228238
space: 'b-w',
229239
width: 32,
230240
autoOrient: {
@@ -250,6 +260,7 @@ describe('Image metadata', () => {
250260
height: 32,
251261
isPalette: false,
252262
isProgressive: false,
263+
mediaType: 'image/png',
253264
space: 'grey16',
254265
width: 32,
255266
autoOrient: {
@@ -263,6 +274,7 @@ describe('Image metadata', () => {
263274
sharp(fixtures.inputWebP).metadata((err, metadata) => {
264275
if (err) throw err;
265276
assert.strictEqual('webp', metadata.format);
277+
assert.strictEqual('image/webp', metadata.mediaType);
266278
assert.strictEqual('undefined', typeof metadata.size);
267279
assert.strictEqual(1024, metadata.width);
268280
assert.strictEqual(772, metadata.height);
@@ -285,11 +297,12 @@ describe('Image metadata', () => {
285297
sharp(fixtures.inputWebPAnimated)
286298
.metadata()
287299
.then(({
288-
format, width, height, space, channels, depth,
300+
format, mediaType, width, height, space, channels, depth,
289301
isProgressive, pages, loop, delay, hasProfile,
290302
hasAlpha
291303
}) => {
292304
assert.strictEqual(format, 'webp');
305+
assert.strictEqual(mediaType, 'image/webp');
293306
assert.strictEqual(width, 80);
294307
assert.strictEqual(height, 80);
295308
assert.strictEqual(space, 'srgb');
@@ -308,11 +321,12 @@ describe('Image metadata', () => {
308321
sharp(fixtures.inputWebPAnimated, { pages: -1 })
309322
.metadata()
310323
.then(({
311-
format, width, height, space, channels, depth,
324+
format, mediaType, width, height, space, channels, depth,
312325
isProgressive, pages, pageHeight, loop, delay,
313326
hasProfile, hasAlpha
314327
}) => {
315328
assert.strictEqual(format, 'webp');
329+
assert.strictEqual(mediaType, 'image/webp');
316330
assert.strictEqual(width, 80);
317331
assert.strictEqual(height, 720);
318332
assert.strictEqual(space, 'srgb');
@@ -332,11 +346,12 @@ describe('Image metadata', () => {
332346
sharp(fixtures.inputWebPAnimatedLoop3)
333347
.metadata()
334348
.then(({
335-
format, width, height, space, channels, depth,
349+
format, mediaType, width, height, space, channels, depth,
336350
isProgressive, pages, loop, delay, hasProfile,
337351
hasAlpha
338352
}) => {
339353
assert.strictEqual(format, 'webp');
354+
assert.strictEqual(mediaType, 'image/webp');
340355
assert.strictEqual(width, 370);
341356
assert.strictEqual(height, 285);
342357
assert.strictEqual(space, 'srgb');
@@ -355,6 +370,7 @@ describe('Image metadata', () => {
355370
sharp(fixtures.inputGif).metadata((err, metadata) => {
356371
if (err) throw err;
357372
assert.strictEqual('gif', metadata.format);
373+
assert.strictEqual('image/gif', metadata.mediaType);
358374
assert.strictEqual('undefined', typeof metadata.size);
359375
assert.strictEqual(800, metadata.width);
360376
assert.strictEqual(533, metadata.height);
@@ -375,6 +391,7 @@ describe('Image metadata', () => {
375391
sharp(fixtures.inputGifGreyPlusAlpha).metadata((err, metadata) => {
376392
if (err) throw err;
377393
assert.strictEqual('gif', metadata.format);
394+
assert.strictEqual('image/gif', metadata.mediaType);
378395
assert.strictEqual('undefined', typeof metadata.size);
379396
assert.strictEqual(2, metadata.width);
380397
assert.strictEqual(1, metadata.height);
@@ -395,11 +412,12 @@ describe('Image metadata', () => {
395412
sharp(fixtures.inputGifAnimated)
396413
.metadata()
397414
.then(({
398-
format, width, height, space, channels, depth,
415+
format, mediaType, width, height, space, channels, depth,
399416
isProgressive, pages, loop, delay, background,
400417
hasProfile, hasAlpha
401418
}) => {
402419
assert.strictEqual(format, 'gif');
420+
assert.strictEqual(mediaType, 'image/gif');
403421
assert.strictEqual(width, 80);
404422
assert.strictEqual(height, 80);
405423
assert.strictEqual(space, 'srgb');
@@ -419,11 +437,12 @@ describe('Image metadata', () => {
419437
sharp(fixtures.inputGifAnimatedLoop3)
420438
.metadata()
421439
.then(({
422-
format, width, height, space, channels, depth,
440+
format, mediaType, width, height, space, channels, depth,
423441
isProgressive, pages, loop, delay, hasProfile,
424442
hasAlpha
425443
}) => {
426444
assert.strictEqual(format, 'gif');
445+
assert.strictEqual(mediaType, 'image/gif');
427446
assert.strictEqual(width, 370);
428447
assert.strictEqual(height, 285);
429448
assert.strictEqual(space, 'srgb');
@@ -462,6 +481,7 @@ describe('Image metadata', () => {
462481
it('File in, Promise out', (_t, done) => {
463482
sharp(fixtures.inputJpg).metadata().then((metadata) => {
464483
assert.strictEqual('jpeg', metadata.format);
484+
assert.strictEqual('image/jpeg', metadata.mediaType);
465485
assert.strictEqual('undefined', typeof metadata.size);
466486
assert.strictEqual(2725, metadata.width);
467487
assert.strictEqual(2225, metadata.height);
@@ -508,6 +528,7 @@ describe('Image metadata', () => {
508528
const pipeline = sharp();
509529
pipeline.metadata().then((metadata) => {
510530
assert.strictEqual('jpeg', metadata.format);
531+
assert.strictEqual('image/jpeg', metadata.mediaType);
511532
assert.strictEqual(829183, metadata.size);
512533
assert.strictEqual(2725, metadata.width);
513534
assert.strictEqual(2225, metadata.height);
@@ -559,6 +580,7 @@ describe('Image metadata', () => {
559580
const pipeline = sharp().metadata((err, metadata) => {
560581
if (err) throw err;
561582
assert.strictEqual('jpeg', metadata.format);
583+
assert.strictEqual('image/jpeg', metadata.mediaType);
562584
assert.strictEqual(829183, metadata.size);
563585
assert.strictEqual(2725, metadata.width);
564586
assert.strictEqual(2225, metadata.height);
@@ -583,6 +605,7 @@ describe('Image metadata', () => {
583605
image.metadata((err, metadata) => {
584606
if (err) throw err;
585607
assert.strictEqual('jpeg', metadata.format);
608+
assert.strictEqual('image/jpeg', metadata.mediaType);
586609
assert.strictEqual('undefined', typeof metadata.size);
587610
assert.strictEqual(2725, metadata.width);
588611
assert.strictEqual(2225, metadata.height);
@@ -917,6 +940,7 @@ describe('Image metadata', () => {
917940
.metadata()
918941
.then(metadata => {
919942
assert.strictEqual(metadata.format, 'tiff');
943+
assert.strictEqual(metadata.mediaType, 'image/tiff');
920944
assert.strictEqual(metadata.width, 317);
921945
assert.strictEqual(metadata.height, 211);
922946
assert.strictEqual(metadata.space, 'rgb16');
@@ -931,6 +955,7 @@ describe('Image metadata', () => {
931955
const metadata = await sharp(fixtures.inputAvif).metadata();
932956
assert.deepStrictEqual(metadata, {
933957
format: 'heif',
958+
mediaType: 'image/avif',
934959
width: 2048,
935960
height: 858,
936961
space: 'srgb',
@@ -1014,6 +1039,7 @@ describe('Image metadata', () => {
10141039
const metadata = await sharp(fixtures.inputJpgLossless).metadata();
10151040
assert.deepStrictEqual(metadata, {
10161041
format: 'jpeg',
1042+
mediaType: 'image/jpeg',
10171043
width: 227,
10181044
height: 149,
10191045
space: 'srgb',

0 commit comments

Comments
 (0)