Skip to content

Commit

Permalink
refactor: migrate media repository (immich-app#15536)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrasm91 authored and vladd11 committed Jan 25, 2025
1 parent 3dfc78b commit 803d337
Show file tree
Hide file tree
Showing 14 changed files with 154 additions and 20 deletions.
2 changes: 1 addition & 1 deletion server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
VideoContainer,
} from 'src/enum';
import { ConcurrentQueueName, QueueName } from 'src/interfaces/job.interface';
import { ImageOptions } from 'src/interfaces/media.interface';
import { ImageOptions } from 'src/types';

export interface SystemConfig {
backup: {
Expand Down
3 changes: 1 addition & 2 deletions server/src/repositories/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { ILibraryRepository } from 'src/interfaces/library.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
import { IMapRepository } from 'src/interfaces/map.interface';
import { IMediaRepository } from 'src/interfaces/media.interface';
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
import { IMoveRepository } from 'src/interfaces/move.interface';
import { INotificationRepository } from 'src/interfaces/notification.interface';
Expand Down Expand Up @@ -77,6 +76,7 @@ export const repositories = [
AuditRepository,
ApiKeyRepository,
ConfigRepository,
MediaRepository,
MemoryRepository,
ViewRepository,
];
Expand All @@ -94,7 +94,6 @@ export const providers = [
{ provide: ILoggerRepository, useClass: LoggerRepository },
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository },
{ provide: IMapRepository, useClass: MapRepository },
{ provide: IMediaRepository, useClass: MediaRepository },
{ provide: IMetadataRepository, useClass: MetadataRepository },
{ provide: IMoveRepository, useClass: MoveRepository },
{ provide: INotificationRepository, useClass: NotificationRepository },
Expand Down
5 changes: 2 additions & 3 deletions server/src/repositories/media.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ import {
DecodeToBufferOptions,
GenerateThumbhashOptions,
GenerateThumbnailOptions,
IMediaRepository,
ImageDimensions,
ProbeOptions,
TranscodeCommand,
VideoInfo,
} from 'src/interfaces/media.interface';
} from 'src/types';
import { handlePromiseError } from 'src/utils/misc';

const probe = (input: string, options: string[]): Promise<FfprobeData> =>
Expand All @@ -37,7 +36,7 @@ type ProgressEvent = {
};

@Injectable()
export class MediaRepository implements IMediaRepository {
export class MediaRepository {
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) {
this.logger.setContext(MediaRepository.name);
}
Expand Down
4 changes: 2 additions & 2 deletions server/src/services/base.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import { ILibraryRepository } from 'src/interfaces/library.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
import { IMapRepository } from 'src/interfaces/map.interface';
import { IMediaRepository } from 'src/interfaces/media.interface';
import { IMetadataRepository } from 'src/interfaces/metadata.interface';
import { IMoveRepository } from 'src/interfaces/move.interface';
import { INotificationRepository } from 'src/interfaces/notification.interface';
Expand All @@ -43,6 +42,7 @@ import { ActivityRepository } from 'src/repositories/activity.repository';
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
import { AuditRepository } from 'src/repositories/audit.repository';
import { ConfigRepository } from 'src/repositories/config.repository';
import { MediaRepository } from 'src/repositories/media.repository';
import { MemoryRepository } from 'src/repositories/memory.repository';
import { ViewRepository } from 'src/repositories/view-repository';
import { AccessRequest, checkAccess, requireAccess } from 'src/utils/access';
Expand All @@ -69,7 +69,7 @@ export class BaseService {
@Inject(ILibraryRepository) protected libraryRepository: ILibraryRepository,
@Inject(IMachineLearningRepository) protected machineLearningRepository: IMachineLearningRepository,
@Inject(IMapRepository) protected mapRepository: IMapRepository,
@Inject(IMediaRepository) protected mediaRepository: IMediaRepository,
protected mediaRepository: MediaRepository,
protected memoryRepository: MemoryRepository,
@Inject(IMetadataRepository) protected metadataRepository: IMetadataRepository,
@Inject(IMoveRepository) protected moveRepository: IMoveRepository,
Expand Down
5 changes: 3 additions & 2 deletions server/src/services/media.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { OutputInfo } from 'sharp';
import { SystemConfig } from 'src/config';
import { AssetEntity } from 'src/entities/asset.entity';
import { ExifEntity } from 'src/entities/exif.entity';
Expand All @@ -15,12 +16,12 @@ import {
import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface';
import { IJobRepository, JobCounts, JobName, JobStatus } from 'src/interfaces/job.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IMediaRepository, RawImageInfo } from 'src/interfaces/media.interface';
import { IMoveRepository } from 'src/interfaces/move.interface';
import { IPersonRepository } from 'src/interfaces/person.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { MediaService } from 'src/services/media.service';
import { IMediaRepository, RawImageInfo } from 'src/types';
import { assetStub } from 'test/fixtures/asset.stub';
import { faceStub } from 'test/fixtures/face.stub';
import { probeStub } from 'test/fixtures/media.stub';
Expand Down Expand Up @@ -257,7 +258,7 @@ describe(MediaService.name, () => {
beforeEach(() => {
rawBuffer = Buffer.from('image data');
rawInfo = { width: 100, height: 100, channels: 3 };
mediaMock.decodeImage.mockResolvedValue({ data: rawBuffer, info: rawInfo });
mediaMock.decodeImage.mockResolvedValue({ data: rawBuffer, info: rawInfo as OutputInfo });
});

it('should skip thumbnail generation if asset not found', async () => {
Expand Down
2 changes: 1 addition & 1 deletion server/src/services/media.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import {
JobStatus,
QueueName,
} from 'src/interfaces/job.interface';
import { AudioStreamInfo, VideoFormat, VideoInterfaces, VideoStreamInfo } from 'src/interfaces/media.interface';
import { BaseService } from 'src/services/base.service';
import { AudioStreamInfo, VideoFormat, VideoInterfaces, VideoStreamInfo } from 'src/types';
import { getAssetFiles } from 'src/utils/asset.util';
import { BaseConfig, ThumbnailConfig } from 'src/utils/media';
import { mimeTypes } from 'src/utils/mime-types';
Expand Down
3 changes: 1 addition & 2 deletions server/src/services/metadata.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,14 @@ import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IEventRepository } from 'src/interfaces/event.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
import { IMapRepository } from 'src/interfaces/map.interface';
import { IMediaRepository } from 'src/interfaces/media.interface';
import { IMetadataRepository, ImmichTags } from 'src/interfaces/metadata.interface';
import { IPersonRepository } from 'src/interfaces/person.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { ITagRepository } from 'src/interfaces/tag.interface';
import { IUserRepository } from 'src/interfaces/user.interface';
import { MetadataService } from 'src/services/metadata.service';
import { IConfigRepository } from 'src/types';
import { IConfigRepository, IMediaRepository } from 'src/types';
import { assetStub } from 'test/fixtures/asset.stub';
import { fileStub } from 'test/fixtures/file.stub';
import { probeStub } from 'test/fixtures/media.stub';
Expand Down
2 changes: 1 addition & 1 deletion server/src/services/person.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interfac
import { ICryptoRepository } from 'src/interfaces/crypto.interface';
import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface';
import { DetectedFaces, IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
import { IMediaRepository } from 'src/interfaces/media.interface';
import { IPersonRepository } from 'src/interfaces/person.interface';
import { FaceSearchResult, ISearchRepository } from 'src/interfaces/search.interface';
import { IStorageRepository } from 'src/interfaces/storage.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { PersonService } from 'src/services/person.service';
import { IMediaRepository } from 'src/types';
import { ImmichFileResponse } from 'src/utils/file';
import { assetStub } from 'test/fixtures/asset.stub';
import { authStub } from 'test/fixtures/auth.stub';
Expand Down
2 changes: 1 addition & 1 deletion server/src/services/person.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ import {
QueueName,
} from 'src/interfaces/job.interface';
import { BoundingBox } from 'src/interfaces/machine-learning.interface';
import { CropOptions, ImageDimensions, InputDimensions } from 'src/interfaces/media.interface';
import { UpdateFacesData } from 'src/interfaces/person.interface';
import { BaseService } from 'src/services/base.service';
import { CropOptions, ImageDimensions, InputDimensions } from 'src/types';
import { getAssetFiles } from 'src/utils/asset.util';
import { ImmichFileResponse } from 'src/utils/file';
import { mimeTypes } from 'src/utils/mime-types';
Expand Down
136 changes: 135 additions & 1 deletion server/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { UserEntity } from 'src/entities/user.entity';
import { Permission } from 'src/enum';
import { ExifOrientation, ImageFormat, Permission, TranscodeTarget, VideoCodec } from 'src/enum';
import { AccessRepository } from 'src/repositories/access.repository';
import { ActivityRepository } from 'src/repositories/activity.repository';
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
import { AuditRepository } from 'src/repositories/audit.repository';
import { ConfigRepository } from 'src/repositories/config.repository';
import { MediaRepository } from 'src/repositories/media.repository';
import { MemoryRepository } from 'src/repositories/memory.repository';
import { ViewRepository } from 'src/repositories/view-repository';

Expand All @@ -24,6 +25,7 @@ export type IAccessRepository = { [K in keyof AccessRepository]: RepositoryInter
export type IApiKeyRepository = RepositoryInterface<ApiKeyRepository>;
export type IAuditRepository = RepositoryInterface<AuditRepository>;
export type IConfigRepository = RepositoryInterface<ConfigRepository>;
export type IMediaRepository = RepositoryInterface<MediaRepository>;
export type IMemoryRepository = RepositoryInterface<MemoryRepository>;
export type IViewRepository = RepositoryInterface<ViewRepository>;

Expand All @@ -39,3 +41,135 @@ export type ApiKeyItem =
export type MemoryItem =
| Awaited<ReturnType<IMemoryRepository['create']>>
| Awaited<ReturnType<IMemoryRepository['search']>>[0];

export interface CropOptions {
top: number;
left: number;
width: number;
height: number;
}

export interface ImageOptions {
format: ImageFormat;
quality: number;
size: number;
}

export interface RawImageInfo {
width: number;
height: number;
channels: 1 | 2 | 3 | 4;
}

interface DecodeImageOptions {
colorspace: string;
crop?: CropOptions;
processInvalidImages: boolean;
raw?: RawImageInfo;
}

export interface DecodeToBufferOptions extends DecodeImageOptions {
size: number;
orientation?: ExifOrientation;
}

export type GenerateThumbnailOptions = ImageOptions & DecodeImageOptions;

export type GenerateThumbnailFromBufferOptions = GenerateThumbnailOptions & { raw: RawImageInfo };

export type GenerateThumbhashOptions = DecodeImageOptions;

export type GenerateThumbhashFromBufferOptions = GenerateThumbhashOptions & { raw: RawImageInfo };

export interface GenerateThumbnailsOptions {
colorspace: string;
crop?: CropOptions;
preview?: ImageOptions;
processInvalidImages: boolean;
thumbhash?: boolean;
thumbnail?: ImageOptions;
}

export interface VideoStreamInfo {
index: number;
height: number;
width: number;
rotation: number;
codecName?: string;
frameCount: number;
isHDR: boolean;
bitrate: number;
pixelFormat: string;
}

export interface AudioStreamInfo {
index: number;
codecName?: string;
frameCount: number;
}

export interface VideoFormat {
formatName?: string;
formatLongName?: string;
duration: number;
bitrate: number;
}

export interface ImageDimensions {
width: number;
height: number;
}

export interface InputDimensions extends ImageDimensions {
inputPath: string;
}

export interface VideoInfo {
format: VideoFormat;
videoStreams: VideoStreamInfo[];
audioStreams: AudioStreamInfo[];
}

export interface TranscodeCommand {
inputOptions: string[];
outputOptions: string[];
twoPass: boolean;
progress: {
frameCount: number;
percentInterval: number;
};
}

export interface BitrateDistribution {
max: number;
target: number;
min: number;
unit: string;
}

export interface ImageBuffer {
data: Buffer;
info: RawImageInfo;
}

export interface VideoCodecSWConfig {
getCommand(
target: TranscodeTarget,
videoStream: VideoStreamInfo,
audioStream: AudioStreamInfo,
format?: VideoFormat,
): TranscodeCommand;
}

export interface VideoCodecHWConfig extends VideoCodecSWConfig {
getSupportedCodecs(): Array<VideoCodec>;
}

export interface ProbeOptions {
countFrames: boolean;
}

export interface VideoInterfaces {
dri: string[];
mali: boolean;
}
2 changes: 1 addition & 1 deletion server/src/utils/media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
VideoFormat,
VideoInterfaces,
VideoStreamInfo,
} from 'src/interfaces/media.interface';
} from 'src/types';

export class BaseConfig implements VideoCodecSWConfig {
readonly presets = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast'];
Expand Down
2 changes: 1 addition & 1 deletion server/test/fixtures/media.stub.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AudioStreamInfo, VideoFormat, VideoInfo, VideoStreamInfo } from 'src/interfaces/media.interface';
import { AudioStreamInfo, VideoFormat, VideoInfo, VideoStreamInfo } from 'src/types';

const probeStubDefaultFormat: VideoFormat = {
formatName: 'mov,mp4,m4a,3gp,3g2,mj2',
Expand Down
2 changes: 1 addition & 1 deletion server/test/repositories/media.repository.mock.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IMediaRepository } from 'src/interfaces/media.interface';
import { IMediaRepository } from 'src/types';
import { Mocked, vitest } from 'vitest';

export const newMediaRepositoryMock = (): Mocked<IMediaRepository> => {
Expand Down
4 changes: 3 additions & 1 deletion server/test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AccessRepository } from 'src/repositories/access.repository';
import { ActivityRepository } from 'src/repositories/activity.repository';
import { ApiKeyRepository } from 'src/repositories/api-key.repository';
import { AuditRepository } from 'src/repositories/audit.repository';
import { MediaRepository } from 'src/repositories/media.repository';
import { MemoryRepository } from 'src/repositories/memory.repository';
import { ViewRepository } from 'src/repositories/view-repository';
import { BaseService } from 'src/services/base.service';
Expand All @@ -15,6 +16,7 @@ import {
IActivityRepository,
IApiKeyRepository,
IAuditRepository,
IMediaRepository,
IMemoryRepository,
IViewRepository,
} from 'src/types';
Expand Down Expand Up @@ -133,7 +135,7 @@ export const newTestService = <T extends BaseService>(
libraryMock,
machineLearningMock,
mapMock,
mediaMock,
mediaMock as IMediaRepository as MediaRepository,
memoryMock as IMemoryRepository as MemoryRepository,
metadataMock,
moveMock,
Expand Down

0 comments on commit 803d337

Please sign in to comment.