1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-04-21 15:36:26 +02:00

refactor: migrate media repository ()

This commit is contained in:
Jason Rasmussen 2025-01-22 17:11:07 -05:00 committed by GitHub
parent 30b8864d2d
commit 66849d0d45
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 154 additions and 171 deletions

View file

@ -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: {

View file

@ -1,151 +0,0 @@
import { Writable } from 'node:stream';
import { ExifOrientation, ImageFormat, TranscodeTarget, VideoCodec } from 'src/enum';
export const IMediaRepository = 'IMediaRepository';
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;
}
export interface IMediaRepository {
// image
extract(input: string, output: string): Promise<boolean>;
decodeImage(input: string, options: DecodeToBufferOptions): Promise<ImageBuffer>;
generateThumbnail(input: string, options: GenerateThumbnailOptions, outputFile: string): Promise<void>;
generateThumbnail(input: Buffer, options: GenerateThumbnailFromBufferOptions, outputFile: string): Promise<void>;
generateThumbhash(input: string, options: GenerateThumbhashOptions): Promise<Buffer>;
generateThumbhash(input: Buffer, options: GenerateThumbhashFromBufferOptions): Promise<Buffer>;
getImageDimensions(input: string): Promise<ImageDimensions>;
// video
probe(input: string, options?: ProbeOptions): Promise<VideoInfo>;
transcode(input: string, output: string | Writable, command: TranscodeCommand): Promise<void>;
}

View file

@ -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';
@ -77,6 +76,7 @@ export const repositories = [
AuditRepository,
ApiKeyRepository,
ConfigRepository,
MediaRepository,
MemoryRepository,
ViewRepository,
];
@ -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 },

View file

@ -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> =>
@ -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);
}

View file

@ -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';
@ -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';
@ -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,

View file

@ -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';
@ -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';
@ -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 () => {

View file

@ -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';

View file

@ -11,7 +11,6 @@ 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';
@ -19,7 +18,7 @@ import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interf
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';

View file

@ -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';

View file

@ -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';

View file

@ -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';
@ -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>;
@ -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;
}

View file

@ -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'];

View file

@ -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',

View file

@ -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> => {

View file

@ -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';
@ -15,6 +16,7 @@ import {
IActivityRepository,
IApiKeyRepository,
IAuditRepository,
IMediaRepository,
IMemoryRepository,
IViewRepository,
} from 'src/types';
@ -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,