Compare commits

..

1 Commits

Author SHA1 Message Date
Daniel Dietzler
cc27b11827 chore: medium tests system config helpers 2026-07-23 21:50:51 +02:00
7 changed files with 45 additions and 54 deletions

View File

@@ -23,7 +23,7 @@ opentofu = "1.12.4"
"github:extism/cli" = "1.6.3"
"github:webassembly/binaryen" = "version_124"
"github:extism/js-pdk" = "1.6.0"
java = "21.0.12+8.0.LTS"
java = "21.0.2"
[tools."github:jellyfin/jellyfin-ffmpeg"]
version = "7.1.3-6"

View File

@@ -18,21 +18,15 @@ struct ImmichWidgetView: View {
var body: some View {
if entry.image == nil {
Image("LaunchImage")
.tintedWidgetImageModifier()
.overlay(alignment: .bottom) {
if let error = entry.metadata.error?.errorDescription {
Text(error)
.minimumScaleFactor(0.25)
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
.fixedSize()
.alignmentGuide(.bottom) { dimensions in
// Place the text below the bottom of the image
dimensions[.top] - 8
}
}
}
VStack {
Image("LaunchImage")
.tintedWidgetImageModifier()
Text(entry.metadata.error?.errorDescription ?? "")
.minimumScaleFactor(0.25)
.multilineTextAlignment(.center)
.foregroundStyle(.secondary)
}
.padding(16)
} else {
ZStack(alignment: .leading) {
Color.clear.overlay(

View File

@@ -1,25 +0,0 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await sql`
INSERT INTO "user_metadata" ("userId", "key", "value")
SELECT "user"."id", 'preferences', jsonb_build_object('people', jsonb_build_object('minimumFaces', "config"."minFaces"))
FROM "user"
CROSS JOIN (
SELECT "value"->'machineLearning'->'facialRecognition'->'minFaces' AS "minFaces"
FROM "system_metadata"
WHERE "key" = 'system-config'
AND "value"->'machineLearning'->'facialRecognition'->'minFaces' IS NOT NULL
) AS "config"
ON CONFLICT ("userId", "key") DO UPDATE
SET "value" = "user_metadata"."value" || jsonb_build_object(
'people',
COALESCE("user_metadata"."value"->'people', '{}'::jsonb) || (EXCLUDED."value"->'people')
)
WHERE "user_metadata"."value"->'people'->'minimumFaces' IS NULL
`.execute(db);
}
export async function down(): Promise<void> {
// not supported
}

View File

@@ -1507,17 +1507,12 @@ describe(MediaService.name, () => {
});
describe('handleGeneratePersonThumbnail', () => {
it('should generate a thumbnail even if machine learning is disabled', async () => {
it('should skip if machine learning is disabled', async () => {
mocks.systemMetadata.get.mockResolvedValue(systemConfigStub.machineLearningDisabled);
mocks.person.getDataForThumbnailGenerationJob.mockResolvedValue(personThumbnailStub.newThumbnailMiddle);
mocks.media.generateThumbnail.mockResolvedValue();
mocks.media.decodeImage.mockResolvedValue({
data: Buffer.from(''),
info: { width: 1000, height: 1000 } as OutputInfo,
});
await expect(sut.handleGeneratePersonThumbnail({ id: 'person-1' })).resolves.toBe(JobStatus.Success);
expect(mocks.media.generateThumbnail).toHaveBeenCalled();
await expect(sut.handleGeneratePersonThumbnail({ id: 'person-1' })).resolves.toBe(JobStatus.Skipped);
expect(mocks.asset.getByIds).not.toHaveBeenCalled();
expect(mocks.systemMetadata.get).toHaveBeenCalled();
});
it('should skip a person not found', async () => {

View File

@@ -43,7 +43,7 @@ import { getAssetFile, getDimensions } from 'src/utils/asset.util';
import { checkFaceVisibility, checkOcrVisibility } from 'src/utils/editor';
import { BaseConfig, ThumbnailConfig } from 'src/utils/media';
import { mimeTypes } from 'src/utils/mime-types';
import { clamp } from 'src/utils/misc';
import { clamp, isFaceImportEnabled, isFacialRecognitionEnabled } from 'src/utils/misc';
import { getOutputDimensions } from 'src/utils/transform';
interface UpsertFileOptions {
@@ -410,7 +410,11 @@ export class MediaService extends BaseService {
@OnJob({ name: JobName.PersonGenerateThumbnail, queue: QueueName.ThumbnailGeneration })
async handleGeneratePersonThumbnail({ id }: JobOf<JobName.PersonGenerateThumbnail>): Promise<JobStatus> {
const { image } = await this.getConfig({ withCache: true });
const { machineLearning, metadata, image } = await this.getConfig({ withCache: true });
if (!isFacialRecognitionEnabled(machineLearning) && !isFaceImportEnabled(metadata)) {
return JobStatus.Skipped;
}
const data = await this.personRepository.getDataForThumbnailGenerationJob(id);
if (!data) {
this.logger.error(`Could not generate person thumbnail for ${id}: missing data`);

View File

@@ -4,6 +4,7 @@ import { createHash, randomBytes } from 'node:crypto';
import { Stats } from 'node:fs';
import { resolve } from 'node:path';
import { Writable } from 'node:stream';
import { SystemConfig } from 'src/config';
import { AssetFace } from 'src/database';
import { AuthDto, LoginResponseDto } from 'src/dtos/auth.dto';
import { AssetEditActionItem, AssetEditsCreateDto } from 'src/dtos/editing.dto';
@@ -76,6 +77,7 @@ import { BASE_SERVICE_DEPENDENCIES, BaseService } from 'src/services/base.servic
import { MetadataService } from 'src/services/metadata.service';
import { SyncService } from 'src/services/sync.service';
import { ClassConstructor, UploadFile } from 'src/types';
import { getConfig, updateConfig } from 'src/utils/config';
import { mockEnvData } from 'test/repositories/config.repository.mock';
import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock';
import { factory, newDate, newEmbedding, newUuid } from 'test/small.factory';
@@ -302,6 +304,28 @@ export class MediumTestContext<S extends BaseService = BaseService> {
const edits = await this.get(AssetEditRepository).replaceAll(assetId, dto.edits as AssetEditActionItem[]);
return { edits };
}
async getConfig({ withCache = true }: { withCache?: boolean } = {}) {
return getConfig(
{
configRepo: this.get(ConfigRepository),
metadataRepo: this.get(SystemMetadataRepository),
logger: this.get(LoggingRepository),
},
{ withCache },
);
}
async updateConfig(config: SystemConfig) {
return updateConfig(
{
configRepo: this.get(ConfigRepository),
metadataRepo: this.get(SystemMetadataRepository),
logger: this.get(LoggingRepository),
},
config,
);
}
}
export class SyncTestContext extends MediumTestContext<SyncService> {

View File

@@ -63,7 +63,6 @@
import { toTimelineAsset } from '$lib/utils/timeline-util';
import type { AssetResponseDto, SharedLinkResponseDto } from '@immich/sdk';
import { untrack, type Snippet } from 'svelte';
import { languageManager } from '$lib/managers/language-manager.svelte';
type Props = {
asset: AssetResponseDto;
@@ -233,7 +232,7 @@
style:width={rasterWidth}
style:height={rasterHeight}
style:transform="scale({rasterScale})"
style:transform-origin={languageManager.rtl ? 'right top' : 'left top'}
style:transform-origin="0 0"
style:will-change={maxRasterPixels > 0 ? 'transform' : undefined}
>
{#if show.alphaBackground}