diff --git a/prisma/migrations/20250827234055_files_default_compression_format/migration.sql b/prisma/migrations/20250827234055_files_default_compression_format/migration.sql new file mode 100644 index 00000000..baffa29d --- /dev/null +++ b/prisma/migrations/20250827234055_files_default_compression_format/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "public"."Zipline" ADD COLUMN "filesDefaultCompressionFormat" TEXT DEFAULT 'jpg'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1b447125..2e7c1db3 100755 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -42,6 +42,7 @@ model Zipline { filesRemoveGpsMetadata Boolean @default(false) filesRandomWordsNumAdjectives Int @default(2) filesRandomWordsSeparator String @default("-") + filesDefaultCompressionFormat String? @default("jpg") urlsRoute String @default("/go") urlsLength Int @default(6) diff --git a/src/components/pages/serverSettings/parts/Files.tsx b/src/components/pages/serverSettings/parts/Files.tsx index 547582ff..a709cf3d 100644 --- a/src/components/pages/serverSettings/parts/Files.tsx +++ b/src/components/pages/serverSettings/parts/Files.tsx @@ -35,6 +35,7 @@ export default function Files({ filesRemoveGpsMetadata: boolean; filesRandomWordsNumAdjectives: number; filesRandomWordsSeparator: string; + filesDefaultCompressionFormat: string; }>({ initialValues: { filesRoute: '/u', @@ -48,6 +49,7 @@ export default function Files({ filesRemoveGpsMetadata: false, filesRandomWordsNumAdjectives: 3, filesRandomWordsSeparator: '-', + filesDefaultCompressionFormat: 'jpg', }, enhanceGetInputProps: (payload) => ({ disabled: data?.tampered?.includes(payload.field) || false, @@ -98,6 +100,7 @@ export default function Files({ filesRemoveGpsMetadata: data.settings.filesRemoveGpsMetadata ?? false, filesRandomWordsNumAdjectives: data.settings.filesRandomWordsNumAdjectives ?? 3, filesRandomWordsSeparator: data.settings.filesRandomWordsSeparator ?? '-', + filesDefaultCompressionFormat: data.settings.filesDefaultCompressionFormat ?? 'jpg', }); }, [data]); @@ -186,6 +189,19 @@ export default function Files({ placeholder='-' {...form.getInputProps('filesRandomWordsSeparator')} /> + + + Compression Format{' '} + {options.imageCompressionFormat !== 'default' ? ( + + saved + + ) : null} + + } + description={ + <> + The image compression format to use only when a compression percent is specified. Leave + at "default" to use the server default compression format. + + } + leftSection={} + value={options.imageCompressionFormat || 'default'} + onChange={(value) => setOption('imageCompressionFormat', (value as any) || 'default')} comboboxProps={{ withinPortal: true, portalProps: { @@ -221,7 +258,7 @@ export default function UploadOptionsButton({ folder, numFiles }: { folder?: str ) : null} } - description='The compression level to use on images (only). Leave blank to disable compression.' + description='The compression level to use on images (only). The above format will be used to compress images. Leave blank to disable compression.' leftSection={} max={100} min={0} diff --git a/src/lib/config/read/db.ts b/src/lib/config/read/db.ts index b656080a..f81ef7f6 100644 --- a/src/lib/config/read/db.ts +++ b/src/lib/config/read/db.ts @@ -28,6 +28,7 @@ export const DATABASE_TO_PROP = { filesRemoveGpsMetadata: 'files.removeGpsMetadata', filesRandomWordsNumAdjectives: 'files.randomWordsNumAdjectives', filesRandomWordsSeparator: 'files.randomWordsSeparator', + filesDefaultCompressionFormat: 'files.defaultCompressionFormat', urlsRoute: 'urls.route', urlsLength: 'urls.length', diff --git a/src/lib/config/read/env.ts b/src/lib/config/read/env.ts index 1993210b..4712014f 100644 --- a/src/lib/config/read/env.ts +++ b/src/lib/config/read/env.ts @@ -57,6 +57,7 @@ export const ENVS = [ env('files.removeGpsMetadata', 'FILES_REMOVE_GPS_METADATA', 'boolean', true), env('files.randomWordsNumAdjectives', 'FILES_RANDOM_WORDS_NUM_ADJECTIVES', 'number', true), env('files.randomWordsSeparator', 'FILES_RANDOM_WORDS_SEPARATOR', 'string', true), + env('files.defaultCompressionFormat', 'FILES_DEFAULT_COMPRESSION_FORMAT', 'string', true), env('urls.route', 'URLS_ROUTE', 'string', true), env('urls.length', 'URLS_LENGTH', 'number', true), diff --git a/src/lib/config/validate.ts b/src/lib/config/validate.ts index d8b5a234..06a92a40 100755 --- a/src/lib/config/validate.ts +++ b/src/lib/config/validate.ts @@ -4,6 +4,7 @@ import { type ZodIssue, z } from 'zod'; import { log } from '../logger'; import { ParsedConfig } from './read'; import { PROP_TO_ENV } from './read/env'; +import { checkOutput, COMPRESS_TYPES } from '../compress'; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -97,6 +98,10 @@ export const schema = z.object({ removeGpsMetadata: z.boolean().default(false), randomWordsNumAdjectives: z.number().default(3), randomWordsSeparator: z.string().default('-'), + defaultCompressionFormat: z + .enum(COMPRESS_TYPES) + .default('jpg') + .refine((v) => checkOutput(v), 'System does not support outputting this image format.'), }), urls: z.object({ route: z.string().startsWith('/').min(1).trim().toLowerCase().default('/go'), diff --git a/src/lib/store/uploadOptions.ts b/src/lib/store/uploadOptions.ts index 810d1534..fd7d3d0c 100755 --- a/src/lib/store/uploadOptions.ts +++ b/src/lib/store/uploadOptions.ts @@ -6,6 +6,7 @@ export const defaultUploadOptions: UploadOptionsStore['options'] = { deletesAt: 'default', format: 'default', imageCompressionPercent: null, + imageCompressionFormat: 'default', maxViews: null, addOriginalName: false, overrides_returnDomain: null, @@ -22,6 +23,7 @@ export type UploadOptionsStore = { deletesAt: string | 'never'; format: Config['files']['defaultFormat'] | 'default'; imageCompressionPercent: number | null; + imageCompressionFormat: Config['files']['defaultCompressionFormat'] | 'default'; maxViews: number | null; addOriginalName: boolean | null; overrides_returnDomain: string | null; diff --git a/src/lib/uploader/parseHeaders.ts b/src/lib/uploader/parseHeaders.ts index 0712f80f..5a08f21f 100755 --- a/src/lib/uploader/parseHeaders.ts +++ b/src/lib/uploader/parseHeaders.ts @@ -1,6 +1,7 @@ import ms from 'ms'; import { Config } from '../config/validate'; import { checkOutput, COMPRESS_TYPES, CompressType } from '../compress'; +import { config } from '../config'; // from ms@3.0.0-canary.1 type Unit = @@ -148,7 +149,7 @@ function parsePercent(header: keyof UploadHeaders, percent: string) { function headerError(header: keyof UploadHeaders, message: string) { return { header, - message: `[${header}]: ${message}` + message: `[${header}]: ${message}`, }; } @@ -215,7 +216,7 @@ export function parseHeaders(headers: UploadHeaders, fileConfig: Config['files'] if (typeof percent === 'object') return percent; response.imageCompression = { - type: 'jpg', + type: config.files.defaultCompressionFormat, percent, }; } diff --git a/src/server/routes/api/server/settings/index.ts b/src/server/routes/api/server/settings/index.ts index 3fe6155f..8c8ce337 100644 --- a/src/server/routes/api/server/settings/index.ts +++ b/src/server/routes/api/server/settings/index.ts @@ -1,4 +1,5 @@ import { bytes } from '@/lib/bytes'; +import { checkOutput, COMPRESS_TYPES } from '@/lib/compress'; import { reloadSettings } from '@/lib/config'; import type { readDatabaseSettings } from '@/lib/config/read/db'; import { safeConfig } from '@/lib/config/safe'; @@ -153,6 +154,9 @@ export default fastifyPlugin( filesRemoveGpsMetadata: z.boolean(), filesRandomWordsNumAdjectives: z.number().min(1).max(20), filesRandomWordsSeparator: z.string(), + filesDefaultCompressionFormat: z + .enum(COMPRESS_TYPES) + .refine((v) => checkOutput(v), 'System does not support outputting this image format.'), urlsRoute: z .string()