mirror of
https://github.com/immich-app/immich.git
synced 2024-11-30 09:47:31 +02:00
feat(web/server): webp thumbnail size configurable (#3598)
* feat(server/web): webp thumbnail size configurable * update api * add ui and fix test * lint * setting for jpeg size * feat: coerce to number * api * jpeg resolution --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
parent
1812e8811b
commit
ddd4ec2d9e
25
cli/src/api/open-api/api.ts
generated
25
cli/src/api/open-api/api.ts
generated
@ -2425,6 +2425,12 @@ export interface SystemConfigDto {
|
|||||||
* @memberof SystemConfigDto
|
* @memberof SystemConfigDto
|
||||||
*/
|
*/
|
||||||
'storageTemplate': SystemConfigStorageTemplateDto;
|
'storageTemplate': SystemConfigStorageTemplateDto;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {SystemConfigThumbnailDto}
|
||||||
|
* @memberof SystemConfigDto
|
||||||
|
*/
|
||||||
|
'thumbnail': SystemConfigThumbnailDto;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -2716,6 +2722,25 @@ export interface SystemConfigTemplateStorageOptionDto {
|
|||||||
*/
|
*/
|
||||||
'yearOptions': Array<string>;
|
'yearOptions': Array<string>;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface SystemConfigThumbnailDto
|
||||||
|
*/
|
||||||
|
export interface SystemConfigThumbnailDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof SystemConfigThumbnailDto
|
||||||
|
*/
|
||||||
|
'jpegSize': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof SystemConfigThumbnailDto
|
||||||
|
*/
|
||||||
|
'webpSize': number;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
3
mobile/openapi/.openapi-generator/FILES
generated
3
mobile/openapi/.openapi-generator/FILES
generated
@ -104,6 +104,7 @@ doc/SystemConfigOAuthDto.md
|
|||||||
doc/SystemConfigPasswordLoginDto.md
|
doc/SystemConfigPasswordLoginDto.md
|
||||||
doc/SystemConfigStorageTemplateDto.md
|
doc/SystemConfigStorageTemplateDto.md
|
||||||
doc/SystemConfigTemplateStorageOptionDto.md
|
doc/SystemConfigTemplateStorageOptionDto.md
|
||||||
|
doc/SystemConfigThumbnailDto.md
|
||||||
doc/TagApi.md
|
doc/TagApi.md
|
||||||
doc/TagResponseDto.md
|
doc/TagResponseDto.md
|
||||||
doc/TagTypeEnum.md
|
doc/TagTypeEnum.md
|
||||||
@ -236,6 +237,7 @@ lib/model/system_config_o_auth_dto.dart
|
|||||||
lib/model/system_config_password_login_dto.dart
|
lib/model/system_config_password_login_dto.dart
|
||||||
lib/model/system_config_storage_template_dto.dart
|
lib/model/system_config_storage_template_dto.dart
|
||||||
lib/model/system_config_template_storage_option_dto.dart
|
lib/model/system_config_template_storage_option_dto.dart
|
||||||
|
lib/model/system_config_thumbnail_dto.dart
|
||||||
lib/model/tag_response_dto.dart
|
lib/model/tag_response_dto.dart
|
||||||
lib/model/tag_type_enum.dart
|
lib/model/tag_type_enum.dart
|
||||||
lib/model/thumbnail_format.dart
|
lib/model/thumbnail_format.dart
|
||||||
@ -355,6 +357,7 @@ test/system_config_o_auth_dto_test.dart
|
|||||||
test/system_config_password_login_dto_test.dart
|
test/system_config_password_login_dto_test.dart
|
||||||
test/system_config_storage_template_dto_test.dart
|
test/system_config_storage_template_dto_test.dart
|
||||||
test/system_config_template_storage_option_dto_test.dart
|
test/system_config_template_storage_option_dto_test.dart
|
||||||
|
test/system_config_thumbnail_dto_test.dart
|
||||||
test/tag_api_test.dart
|
test/tag_api_test.dart
|
||||||
test/tag_response_dto_test.dart
|
test/tag_response_dto_test.dart
|
||||||
test/tag_type_enum_test.dart
|
test/tag_type_enum_test.dart
|
||||||
|
BIN
mobile/openapi/README.md
generated
BIN
mobile/openapi/README.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/SystemConfigDto.md
generated
BIN
mobile/openapi/doc/SystemConfigDto.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/SystemConfigThumbnailDto.md
generated
Normal file
BIN
mobile/openapi/doc/SystemConfigThumbnailDto.md
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/lib/api.dart
generated
BIN
mobile/openapi/lib/api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api_client.dart
generated
BIN
mobile/openapi/lib/api_client.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/model/system_config_dto.dart
generated
BIN
mobile/openapi/lib/model/system_config_dto.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/model/system_config_thumbnail_dto.dart
generated
Normal file
BIN
mobile/openapi/lib/model/system_config_thumbnail_dto.dart
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/test/system_config_dto_test.dart
generated
BIN
mobile/openapi/test/system_config_dto_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/system_config_thumbnail_dto_test.dart
generated
Normal file
BIN
mobile/openapi/test/system_config_thumbnail_dto_test.dart
generated
Normal file
Binary file not shown.
@ -6590,6 +6590,9 @@
|
|||||||
},
|
},
|
||||||
"storageTemplate": {
|
"storageTemplate": {
|
||||||
"$ref": "#/components/schemas/SystemConfigStorageTemplateDto"
|
"$ref": "#/components/schemas/SystemConfigStorageTemplateDto"
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"$ref": "#/components/schemas/SystemConfigThumbnailDto"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@ -6597,7 +6600,8 @@
|
|||||||
"oauth",
|
"oauth",
|
||||||
"passwordLogin",
|
"passwordLogin",
|
||||||
"storageTemplate",
|
"storageTemplate",
|
||||||
"job"
|
"job",
|
||||||
|
"thumbnail"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
@ -6828,6 +6832,21 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"SystemConfigThumbnailDto": {
|
||||||
|
"properties": {
|
||||||
|
"jpegSize": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"webpSize": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"webpSize",
|
||||||
|
"jpegSize"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"TagResponseDto": {
|
"TagResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
|
@ -1,3 +1 @@
|
|||||||
export const JPEG_THUMBNAIL_SIZE = 1440;
|
|
||||||
export const WEBP_THUMBNAIL_SIZE = 250;
|
|
||||||
export const FACE_THUMBNAIL_SIZE = 250;
|
export const FACE_THUMBNAIL_SIZE = 250;
|
||||||
|
@ -7,7 +7,6 @@ import { IBaseJob, IEntityJob, IJobRepository, JobName, JOBS_ASSET_PAGINATION_SI
|
|||||||
import { IStorageRepository, StorageCore, StorageFolder } from '../storage';
|
import { IStorageRepository, StorageCore, StorageFolder } from '../storage';
|
||||||
import { ISystemConfigRepository, SystemConfigFFmpegDto } from '../system-config';
|
import { ISystemConfigRepository, SystemConfigFFmpegDto } from '../system-config';
|
||||||
import { SystemConfigCore } from '../system-config/system-config.core';
|
import { SystemConfigCore } from '../system-config/system-config.core';
|
||||||
import { JPEG_THUMBNAIL_SIZE, WEBP_THUMBNAIL_SIZE } from './media.constant';
|
|
||||||
import { AudioStreamInfo, IMediaRepository, VideoCodecHWConfig, VideoStreamInfo } from './media.repository';
|
import { AudioStreamInfo, IMediaRepository, VideoCodecHWConfig, VideoStreamInfo } from './media.repository';
|
||||||
import { H264Config, HEVCConfig, NVENCConfig, QSVConfig, ThumbnailConfig, VAAPIConfig, VP9Config } from './media.util';
|
import { H264Config, HEVCConfig, NVENCConfig, QSVConfig, ThumbnailConfig, VAAPIConfig, VP9Config } from './media.util';
|
||||||
|
|
||||||
@ -63,11 +62,12 @@ export class MediaService {
|
|||||||
const resizePath = this.storageCore.getFolderLocation(StorageFolder.THUMBNAILS, asset.ownerId);
|
const resizePath = this.storageCore.getFolderLocation(StorageFolder.THUMBNAILS, asset.ownerId);
|
||||||
this.storageRepository.mkdirSync(resizePath);
|
this.storageRepository.mkdirSync(resizePath);
|
||||||
const jpegThumbnailPath = join(resizePath, `${asset.id}.jpeg`);
|
const jpegThumbnailPath = join(resizePath, `${asset.id}.jpeg`);
|
||||||
|
const { thumbnail } = await this.configCore.getConfig();
|
||||||
|
|
||||||
switch (asset.type) {
|
switch (asset.type) {
|
||||||
case AssetType.IMAGE:
|
case AssetType.IMAGE:
|
||||||
await this.mediaRepository.resize(asset.originalPath, jpegThumbnailPath, {
|
await this.mediaRepository.resize(asset.originalPath, jpegThumbnailPath, {
|
||||||
size: JPEG_THUMBNAIL_SIZE,
|
size: thumbnail.jpegSize,
|
||||||
format: 'jpeg',
|
format: 'jpeg',
|
||||||
});
|
});
|
||||||
this.logger.log(`Successfully generated image thumbnail ${asset.id}`);
|
this.logger.log(`Successfully generated image thumbnail ${asset.id}`);
|
||||||
@ -80,7 +80,7 @@ export class MediaService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const { ffmpeg } = await this.configCore.getConfig();
|
const { ffmpeg } = await this.configCore.getConfig();
|
||||||
const config = { ...ffmpeg, targetResolution: JPEG_THUMBNAIL_SIZE.toString(), twoPass: false };
|
const config = { ...ffmpeg, targetResolution: thumbnail.jpegSize.toString(), twoPass: false };
|
||||||
const options = new ThumbnailConfig(config).getOptions(mainVideoStream);
|
const options = new ThumbnailConfig(config).getOptions(mainVideoStream);
|
||||||
await this.mediaRepository.transcode(asset.originalPath, jpegThumbnailPath, options);
|
await this.mediaRepository.transcode(asset.originalPath, jpegThumbnailPath, options);
|
||||||
this.logger.log(`Successfully generated video thumbnail ${asset.id}`);
|
this.logger.log(`Successfully generated video thumbnail ${asset.id}`);
|
||||||
@ -100,7 +100,8 @@ export class MediaService {
|
|||||||
|
|
||||||
const webpPath = asset.resizePath.replace('jpeg', 'webp').replace('jpg', 'webp');
|
const webpPath = asset.resizePath.replace('jpeg', 'webp').replace('jpg', 'webp');
|
||||||
|
|
||||||
await this.mediaRepository.resize(asset.resizePath, webpPath, { size: WEBP_THUMBNAIL_SIZE, format: 'webp' });
|
const { thumbnail } = await this.configCore.getConfig();
|
||||||
|
await this.mediaRepository.resize(asset.resizePath, webpPath, { size: thumbnail.webpSize, format: 'webp' });
|
||||||
await this.assetRepository.save({ id: asset.id, webpPath });
|
await this.assetRepository.save({ id: asset.id, webpPath });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -2,4 +2,5 @@ export * from './system-config-ffmpeg.dto';
|
|||||||
export * from './system-config-oauth.dto';
|
export * from './system-config-oauth.dto';
|
||||||
export * from './system-config-password-login.dto';
|
export * from './system-config-password-login.dto';
|
||||||
export * from './system-config-storage-template.dto';
|
export * from './system-config-storage-template.dto';
|
||||||
|
export * from './system-config-thumbnail.dto';
|
||||||
export * from './system-config.dto';
|
export * from './system-config.dto';
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { IsInt } from 'class-validator';
|
||||||
|
|
||||||
|
export class SystemConfigThumbnailDto {
|
||||||
|
@IsInt()
|
||||||
|
@Type(() => Number)
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
webpSize!: number;
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
@Type(() => Number)
|
||||||
|
@ApiProperty({ type: 'integer' })
|
||||||
|
jpegSize!: number;
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import { SystemConfigThumbnailDto } from '@app/domain/system-config';
|
||||||
import { SystemConfig } from '@app/infra/entities';
|
import { SystemConfig } from '@app/infra/entities';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { IsObject, ValidateNested } from 'class-validator';
|
import { IsObject, ValidateNested } from 'class-validator';
|
||||||
@ -32,6 +33,11 @@ export class SystemConfigDto {
|
|||||||
@ValidateNested()
|
@ValidateNested()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
job!: SystemConfigJobDto;
|
job!: SystemConfigJobDto;
|
||||||
|
|
||||||
|
@Type(() => SystemConfigThumbnailDto)
|
||||||
|
@ValidateNested()
|
||||||
|
@IsObject()
|
||||||
|
thumbnail!: SystemConfigThumbnailDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapConfig(config: SystemConfig): SystemConfigDto {
|
export function mapConfig(config: SystemConfig): SystemConfigDto {
|
||||||
|
@ -64,6 +64,11 @@ export const defaults = Object.freeze<SystemConfig>({
|
|||||||
storageTemplate: {
|
storageTemplate: {
|
||||||
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
thumbnail: {
|
||||||
|
webpSize: 250,
|
||||||
|
jpegSize: 1440,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const singleton = new Subject<SystemConfig>();
|
const singleton = new Subject<SystemConfig>();
|
||||||
|
@ -65,6 +65,10 @@ const updatedConfig = Object.freeze<SystemConfig>({
|
|||||||
storageTemplate: {
|
storageTemplate: {
|
||||||
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
template: '{{y}}/{{y}}-{{MM}}-{{dd}}/{{filename}}',
|
||||||
},
|
},
|
||||||
|
thumbnail: {
|
||||||
|
webpSize: 250,
|
||||||
|
jpegSize: 1440,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(SystemConfigService.name, () => {
|
describe(SystemConfigService.name, () => {
|
||||||
|
@ -52,6 +52,9 @@ export enum SystemConfigKey {
|
|||||||
PASSWORD_LOGIN_ENABLED = 'passwordLogin.enabled',
|
PASSWORD_LOGIN_ENABLED = 'passwordLogin.enabled',
|
||||||
|
|
||||||
STORAGE_TEMPLATE = 'storageTemplate.template',
|
STORAGE_TEMPLATE = 'storageTemplate.template',
|
||||||
|
|
||||||
|
THUMBNAIL_WEBP_SIZE = 'thumbnail.webpSize',
|
||||||
|
THUMBNAIL_JPEG_SIZE = 'thumbnail.jpegSize',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TranscodePolicy {
|
export enum TranscodePolicy {
|
||||||
@ -121,4 +124,8 @@ export interface SystemConfig {
|
|||||||
storageTemplate: {
|
storageTemplate: {
|
||||||
template: string;
|
template: string;
|
||||||
};
|
};
|
||||||
|
thumbnail: {
|
||||||
|
webpSize: number;
|
||||||
|
jpegSize: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ export class MediaRepository implements IMediaRepository {
|
|||||||
private logger = new Logger(MediaRepository.name);
|
private logger = new Logger(MediaRepository.name);
|
||||||
|
|
||||||
crop(input: string, options: CropOptions): Promise<Buffer> {
|
crop(input: string, options: CropOptions): Promise<Buffer> {
|
||||||
return sharp(input, { failOnError: false })
|
return sharp(input, { failOn: 'none' })
|
||||||
.extract({
|
.extract({
|
||||||
left: options.left,
|
left: options.left,
|
||||||
top: options.top,
|
top: options.top,
|
||||||
@ -23,7 +23,7 @@ export class MediaRepository implements IMediaRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async resize(input: string | Buffer, output: string, options: ResizeOptions): Promise<void> {
|
async resize(input: string | Buffer, output: string, options: ResizeOptions): Promise<void> {
|
||||||
await sharp(input, { failOnError: false })
|
await sharp(input, { failOn: 'none' })
|
||||||
.resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true })
|
.resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true })
|
||||||
.rotate()
|
.rotate()
|
||||||
.toFormat(options.format)
|
.toFormat(options.format)
|
||||||
|
25
web/src/api/open-api/api.ts
generated
25
web/src/api/open-api/api.ts
generated
@ -2425,6 +2425,12 @@ export interface SystemConfigDto {
|
|||||||
* @memberof SystemConfigDto
|
* @memberof SystemConfigDto
|
||||||
*/
|
*/
|
||||||
'storageTemplate': SystemConfigStorageTemplateDto;
|
'storageTemplate': SystemConfigStorageTemplateDto;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {SystemConfigThumbnailDto}
|
||||||
|
* @memberof SystemConfigDto
|
||||||
|
*/
|
||||||
|
'thumbnail': SystemConfigThumbnailDto;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -2716,6 +2722,25 @@ export interface SystemConfigTemplateStorageOptionDto {
|
|||||||
*/
|
*/
|
||||||
'yearOptions': Array<string>;
|
'yearOptions': Array<string>;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface SystemConfigThumbnailDto
|
||||||
|
*/
|
||||||
|
export interface SystemConfigThumbnailDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof SystemConfigThumbnailDto
|
||||||
|
*/
|
||||||
|
'jpegSize': number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof SystemConfigThumbnailDto
|
||||||
|
*/
|
||||||
|
'webpSize': number;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="mb-4 w-full">
|
||||||
<div class={`flex h-[26px] place-items-center gap-1`}>
|
<div class={`flex h-[26px] place-items-center gap-1`}>
|
||||||
<label class={`immich-form-label text-sm`} for={label}>{label}</label>
|
<label class={`immich-form-label text-sm`} for={label}>{label}</label>
|
||||||
{#if required}
|
{#if required}
|
||||||
@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if desc}
|
{#if desc}
|
||||||
<p class="immich-form-label pb-2 text-xs" id="{label}-desc">
|
<p class="immich-form-label pb-2 text-sm" id="{label}-desc">
|
||||||
{desc}
|
{desc}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -2,19 +2,23 @@
|
|||||||
import { quintOut } from 'svelte/easing';
|
import { quintOut } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
|
|
||||||
export let value: string;
|
export let value: string | number;
|
||||||
export let options: { value: string; text: string }[];
|
export let options: { value: string | number; text: string }[];
|
||||||
export let label = '';
|
export let label = '';
|
||||||
export let desc = '';
|
export let desc = '';
|
||||||
export let name = '';
|
export let name = '';
|
||||||
export let isEdited = false;
|
export let isEdited = false;
|
||||||
|
export let number = false;
|
||||||
|
|
||||||
const handleChange = (e: Event) => {
|
const handleChange = (e: Event) => {
|
||||||
value = (e.target as HTMLInputElement).value;
|
value = (e.target as HTMLInputElement).value;
|
||||||
|
if (number) {
|
||||||
|
value = parseInt(value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="mb-4 w-full">
|
||||||
<div class={`flex h-[26px] place-items-center gap-1`}>
|
<div class={`flex h-[26px] place-items-center gap-1`}>
|
||||||
<label class={`immich-form-label text-sm`} for="{name}-select">{label}</label>
|
<label class={`immich-form-label text-sm`} for="{name}-select">{label}</label>
|
||||||
|
|
||||||
@ -29,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if desc}
|
{#if desc}
|
||||||
<p class="immich-form-label pb-2 text-xs" id="{name}-desc">
|
<p class="immich-form-label pb-2 text-sm" id="{name}-desc">
|
||||||
{desc}
|
{desc}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -0,0 +1,121 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import SettingSelect from '$lib/components/admin-page/settings/setting-select.svelte';
|
||||||
|
import { api, SystemConfigThumbnailDto } from '@api';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import { isEqual } from 'lodash-es';
|
||||||
|
import SettingButtonsRow from '$lib/components/admin-page/settings/setting-buttons-row.svelte';
|
||||||
|
import {
|
||||||
|
notificationController,
|
||||||
|
NotificationType,
|
||||||
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
|
|
||||||
|
export let thumbnailConfig: SystemConfigThumbnailDto; // this is the config that is being edited
|
||||||
|
|
||||||
|
let savedConfig: SystemConfigThumbnailDto;
|
||||||
|
let defaultConfig: SystemConfigThumbnailDto;
|
||||||
|
|
||||||
|
async function getConfigs() {
|
||||||
|
[savedConfig, defaultConfig] = await Promise.all([
|
||||||
|
api.systemConfigApi.getConfig().then((res) => res.data.thumbnail),
|
||||||
|
api.systemConfigApi.getDefaults().then((res) => res.data.thumbnail),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function reset() {
|
||||||
|
const { data: resetConfig } = await api.systemConfigApi.getConfig();
|
||||||
|
|
||||||
|
thumbnailConfig = { ...resetConfig.thumbnail };
|
||||||
|
savedConfig = { ...resetConfig.thumbnail };
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
message: 'Reset thumbnail settings to the recent saved settings',
|
||||||
|
type: NotificationType.Info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetToDefault() {
|
||||||
|
const { data: configs } = await api.systemConfigApi.getDefaults();
|
||||||
|
|
||||||
|
thumbnailConfig = { ...configs.thumbnail };
|
||||||
|
defaultConfig = { ...configs.thumbnail };
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
message: 'Reset thumbnail settings to default',
|
||||||
|
type: NotificationType.Info,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSetting() {
|
||||||
|
try {
|
||||||
|
const { data: configs } = await api.systemConfigApi.getConfig();
|
||||||
|
|
||||||
|
const result = await api.systemConfigApi.updateConfig({
|
||||||
|
systemConfigDto: {
|
||||||
|
...configs,
|
||||||
|
thumbnail: thumbnailConfig,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
thumbnailConfig = { ...result.data.thumbnail };
|
||||||
|
savedConfig = { ...result.data.thumbnail };
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
message: 'Thumbnail settings saved',
|
||||||
|
type: NotificationType.Info,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error [thumbnail-settings] [saveSetting]', e);
|
||||||
|
notificationController.show({
|
||||||
|
message: 'Unable to save settings',
|
||||||
|
type: NotificationType.Error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#await getConfigs() then}
|
||||||
|
<div in:fade={{ duration: 500 }}>
|
||||||
|
<form autocomplete="off" on:submit|preventDefault>
|
||||||
|
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||||
|
<SettingSelect
|
||||||
|
label="WEBP RESOLUTION"
|
||||||
|
desc="Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness."
|
||||||
|
number
|
||||||
|
bind:value={thumbnailConfig.webpSize}
|
||||||
|
options={[
|
||||||
|
{ value: 1080, text: '1080p' },
|
||||||
|
{ value: 720, text: '720p' },
|
||||||
|
{ value: 480, text: '480p' },
|
||||||
|
{ value: 250, text: '250p' },
|
||||||
|
]}
|
||||||
|
name="resolution"
|
||||||
|
isEdited={!(thumbnailConfig.webpSize === savedConfig.webpSize)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingSelect
|
||||||
|
label="JPEG RESOLUTION"
|
||||||
|
desc="Higher resolutions can preserve more detail but take longer to encode, have larger file sizes, and can reduce app responsiveness."
|
||||||
|
number
|
||||||
|
bind:value={thumbnailConfig.jpegSize}
|
||||||
|
options={[
|
||||||
|
{ value: 2160, text: '4K' },
|
||||||
|
{ value: 1440, text: '1440p' },
|
||||||
|
]}
|
||||||
|
name="resolution"
|
||||||
|
isEdited={!(thumbnailConfig.jpegSize === savedConfig.jpegSize)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-4">
|
||||||
|
<SettingButtonsRow
|
||||||
|
on:reset={reset}
|
||||||
|
on:save={saveSetting}
|
||||||
|
on:reset-to-default={resetToDefault}
|
||||||
|
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/await}
|
||||||
|
</div>
|
@ -2,6 +2,7 @@
|
|||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte';
|
import FFmpegSettings from '$lib/components/admin-page/settings/ffmpeg/ffmpeg-settings.svelte';
|
||||||
import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte';
|
import JobSettings from '$lib/components/admin-page/settings/job-settings/job-settings.svelte';
|
||||||
|
import ThumbnailSettings from '$lib/components/admin-page/settings/thumbnail/thumbnail-settings.svelte';
|
||||||
import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte';
|
import OAuthSettings from '$lib/components/admin-page/settings/oauth/oauth-settings.svelte';
|
||||||
import PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte';
|
import PasswordLoginSettings from '$lib/components/admin-page/settings/password-login/password-login-settings.svelte';
|
||||||
import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte';
|
import SettingAccordion from '$lib/components/admin-page/settings/setting-accordion.svelte';
|
||||||
@ -22,6 +23,10 @@
|
|||||||
{#await getConfig()}
|
{#await getConfig()}
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
{:then configs}
|
{:then configs}
|
||||||
|
<SettingAccordion title="Thumbnail Settings" subtitle="Manage the resolution of thumbnail sizes">
|
||||||
|
<ThumbnailSettings thumbnailConfig={configs.thumbnail} />
|
||||||
|
</SettingAccordion>
|
||||||
|
|
||||||
<SettingAccordion
|
<SettingAccordion
|
||||||
title="FFmpeg Settings"
|
title="FFmpeg Settings"
|
||||||
subtitle="Manage the resolution and encoding information of the video files"
|
subtitle="Manage the resolution and encoding information of the video files"
|
||||||
|
Loading…
Reference in New Issue
Block a user