Skip to content

Commit 6c48bed

Browse files
committed
perf: use webp instead of jpg for images
Improves the loading times and the payload transmitted, also solves one of Lighthouse's concerns Signed-off-by: Fernando Fernández <[email protected]>
1 parent 065b29a commit 6c48bed

File tree

2 files changed

+67
-55
lines changed

2 files changed

+67
-55
lines changed

frontend/src/components/Layout/Images/UserImage.vue

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<VAvatar
33
:size="size">
44
<JImg
5-
:src="url"
5+
:src="getUserImageUrl(remote.auth.currentUser.value)"
66
:alt="$t('userImage')">
77
<template #placeholder>
88
<VAvatar
@@ -18,8 +18,8 @@
1818

1919
<script setup lang="ts">
2020
import type { UserDto } from '@jellyfin/sdk/lib/generated-client';
21-
import { computed } from 'vue';
2221
import { remote } from '#/plugins/remote';
22+
import { getUserImageUrl } from '#/utils/images';
2323
2424
/**
2525
* TODO: In reality, rounded is unnecessary since it can be passed as fallthrough,
@@ -32,13 +32,4 @@ const { user, size = 64, rounded } = defineProps<{
3232
size?: number;
3333
rounded?: boolean;
3434
}>();
35-
36-
const url = computed(() => {
37-
return user.Id && user.PrimaryImageTag && remote.sdk.api?.basePath
38-
? `${remote.sdk.api.basePath}/Users/${user.Id}/Images/Primary/?tag=${user.PrimaryImageTag}`
39-
: undefined;
40-
});
41-
const iconSize = computed(() => {
42-
return size * 0.75;
43-
});
4435
</script>

frontend/src/utils/images.ts

Lines changed: 65 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ import {
66
type BaseItemDto,
77
BaseItemKind,
88
type BaseItemPerson,
9-
ImageType
9+
ImageType,
10+
type UserDto
1011
} from '@jellyfin/sdk/lib/generated-client';
1112
import { getImageApi } from '@jellyfin/sdk/lib/utils/api/image-api';
1213
import type { ImageUrlsApi } from '@jellyfin/sdk/lib/utils/api/image-urls-api';
14+
import type { ImageRequestParameters } from '@jellyfin/sdk/lib/models/api/image-request-parameters';
15+
import { isNil } from '@jellyfin-vue/shared/validation';
1316
import { remote } from '#/plugins/remote';
1417
import { CardShapes, getShapeFromItemType, isPerson } from '#/utils/items';
1518

@@ -22,13 +25,63 @@ const excludedBlurhashTypes = Object.freeze(
2225
new Set<ImageType>([ImageType.Logo])
2326
);
2427

28+
const imageDefaultOptions = (): ImageRequestParameters => ({ format: 'Webp' });
29+
2530
/**
2631
* Gets the image URL given an item id and the image type requested
2732
*/
2833
export function getItemImageUrl(...args: Parameters<ImageUrlsApi['getItemImageUrlById']>) {
34+
const argsLen = args.length;
35+
36+
switch (argsLen) {
37+
case 1: {
38+
args.push(undefined, imageDefaultOptions());
39+
break;
40+
}
41+
case 2: {
42+
args.push(imageDefaultOptions());
43+
break;
44+
}
45+
case 3: {
46+
args[2] = { ...args[2], ...imageDefaultOptions() };
47+
break;
48+
}
49+
}
50+
2951
return remote.sdk.newUserApi(getImageApi).getItemImageUrlById(...args);
3052
}
3153

54+
/**
55+
* Gets the logged's user image URL
56+
*/
57+
export function getUserImageUrl(user?: UserDto) {
58+
return remote.sdk.newUserApi(getImageApi).getUserImageUrl(user, imageDefaultOptions());
59+
}
60+
61+
/**
62+
* Gets the image url with the desired size and quality.
63+
*/
64+
function getImageUrlWithSize(
65+
itemId: string,
66+
{ width,
67+
height,
68+
quality,
69+
ratio = 1
70+
}: {
71+
width?: number;
72+
height?: number;
73+
quality?: number;
74+
ratio?: number;
75+
} = {},
76+
imgType?: ImageType
77+
) {
78+
return getItemImageUrl(itemId, imgType, {
79+
quality,
80+
maxWidth: isNil(width) ? undefined : Math.round(width * ratio),
81+
maxHeight: isNil(height) ? undefined : Math.round(height * ratio)
82+
});
83+
};
84+
3285
/**
3386
* Gets the tag of the image of an specific item and type.
3487
*
@@ -214,7 +267,6 @@ export function getImageInfo(
214267
} = {}
215268
): ImageUrlInfo {
216269
// TODO: Refactor to have separate getPosterImageInfo, getThumbImageInfo and getBackdropImageInfo.
217-
let url;
218270
let imgType;
219271
let imgTag;
220272
let itemId: string | null | undefined = item.Id;
@@ -356,31 +408,13 @@ export function getImageInfo(
356408
};
357409
}
358410

359-
const url_string = getItemImageUrl(itemId, imgType);
360-
361-
if (imgTag && imgType && url_string) {
362-
url = new URL(url_string);
363-
364-
const parameters: Record<string, string> = {
365-
imgTag,
366-
quality: quality.toString()
367-
};
368-
369-
if (width) {
370-
width = Math.round(width * ratio);
371-
parameters.maxWidth = width.toString();
372-
}
373-
374-
if (height) {
375-
height = Math.round(height * ratio);
376-
parameters.maxHeight = height.toString();
377-
}
378-
379-
url.search = new URLSearchParams(parameters).toString();
380-
}
381-
382411
return {
383-
url: url?.href,
412+
url: getImageUrlWithSize(itemId, {
413+
width,
414+
height,
415+
quality,
416+
ratio
417+
}, imgType),
384418
blurhash:
385419
imgType && imgTag ? item.ImageBlurHashes?.[imgType]?.[imgTag] : undefined
386420
};
@@ -411,7 +445,6 @@ export function getLogo(
411445
tag?: string;
412446
} = {}
413447
): ImageUrlInfo {
414-
let url;
415448
let imgType;
416449
let imgTag;
417450
let itemId: string | null | undefined = item.Id;
@@ -428,24 +461,12 @@ export function getLogo(
428461
itemId = item.ParentLogoItemId;
429462
}
430463

431-
if (imgTag && imgType && itemId) {
432-
url = new URL(getItemImageUrl(itemId, imgType));
433-
434-
const parameters: Record<string, string> = {
435-
imgTag,
436-
quality: quality.toString()
437-
};
438-
439-
if (width) {
440-
width = Math.round(width * ratio);
441-
parameters.maxWidth = width.toString();
442-
}
443-
444-
url.search = new URLSearchParams(parameters).toString();
445-
}
446-
447464
return {
448-
url: url?.href,
465+
url: getImageUrlWithSize(itemId ?? '', {
466+
width,
467+
quality,
468+
ratio
469+
}, imgType),
449470
blurhash:
450471
imgType && imgTag ? item.ImageBlurHashes?.[imgType]?.[imgTag] : undefined
451472
};

0 commit comments

Comments
 (0)