mirror of
https://github.com/KevinMidboe/immich.git
synced 2025-10-29 17:40:28 +00:00
WIP refactor container and queuing system (#206)
* refactor microservices to machine-learning * Update tGithub issue template with correct task syntax * Added microservices container * Communicate between service based on queue system * added dependency * Fixed problem with having to import BullQueue into the individual service * Added todo * refactor server into monorepo with microservices * refactor database and entity to library * added simple migration * Move migrations and database config to library * Migration works in library * Cosmetic change in logging message * added user dto * Fixed issue with testing not able to find the shared library * Clean up library mapping path * Added webp generator to microservices * Update Github Action build latest * Fixed issue NPM cannot install due to conflict witl Bull Queue * format project with prettier * Modified docker-compose file * Add GH Action for Staging build: * Fixed GH action job name * Modified GH Action to only build & push latest when pushing to main * Added Test 2e2 Github Action * Added Test 2e2 Github Action * Implemented microservice to extract exif * Added cronjob to scan and generate webp thumbnail at midnight * Refactor to ireduce hit time to database when running microservices * Added error handling to asset services that handle read file from disk * Added video transcoding queue to process one video at a time * Fixed loading spinner on web while loading covering the info panel * Add mechanism to show new release announcement to web and mobile app (#209) * Added changelog page * Fixed issues based on PR comments * Fixed issue with video transcoding run on the server * Change entry point content for backward combatibility when starting up server * Added announcement box * Added error handling to failed silently when the app version checking is not able to make the request to GITHUB * Added new version announcement overlay * Update message * Added messages * Added logic to check and show announcement * Add method to handle saving new version * Added button to dimiss the acknowledge message * Up version for deployment to the app store
This commit is contained in:
16
machine-learning/src/app.module.ts
Normal file
16
machine-learning/src/app.module.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ImageClassifierModule } from './image-classifier/image-classifier.module';
|
||||
import { databaseConfig } from './config/database.config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ObjectDetectionModule } from './object-detection/object-detection.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forRoot(databaseConfig),
|
||||
ImageClassifierModule,
|
||||
ObjectDetectionModule,
|
||||
],
|
||||
controllers: [],
|
||||
providers: [],
|
||||
})
|
||||
export class AppModule {}
|
||||
11
machine-learning/src/config/database.config.ts
Normal file
11
machine-learning/src/config/database.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
||||
|
||||
export const databaseConfig: TypeOrmModuleOptions = {
|
||||
type: 'postgres',
|
||||
host: process.env.DB_HOSTNAME || 'immich_postgres',
|
||||
port: 5432,
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE_NAME,
|
||||
synchronize: false,
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { ImageClassifierService } from './image-classifier.service';
|
||||
|
||||
@Controller('image-classifier')
|
||||
export class ImageClassifierController {
|
||||
constructor(
|
||||
private readonly imageClassifierService: ImageClassifierService,
|
||||
) { }
|
||||
|
||||
@Post('/tag-image')
|
||||
async tagImage(@Body('thumbnailPath') thumbnailPath: string) {
|
||||
return await this.imageClassifierService.tagImage(thumbnailPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ImageClassifierService } from './image-classifier.service';
|
||||
import { ImageClassifierController } from './image-classifier.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [ImageClassifierController],
|
||||
providers: [ImageClassifierService],
|
||||
})
|
||||
export class ImageClassifierModule {}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import * as mobilenet from '@tensorflow-models/mobilenet';
|
||||
import * as cocoSsd from '@tensorflow-models/coco-ssd';
|
||||
import * as tf from '@tensorflow/tfjs-node';
|
||||
import * as fs from 'fs';
|
||||
|
||||
@Injectable()
|
||||
export class ImageClassifierService {
|
||||
private readonly MOBILENET_VERSION = 2;
|
||||
private readonly MOBILENET_ALPHA = 1.0;
|
||||
|
||||
private mobileNetModel: mobilenet.MobileNet;
|
||||
|
||||
constructor() {
|
||||
Logger.log(
|
||||
`Running Node TensorFlow Version : ${tf.version['tfjs']}`,
|
||||
'ImageClassifier',
|
||||
);
|
||||
mobilenet
|
||||
.load({
|
||||
version: this.MOBILENET_VERSION,
|
||||
alpha: this.MOBILENET_ALPHA,
|
||||
})
|
||||
.then((mobilenetModel) => (this.mobileNetModel = mobilenetModel));
|
||||
}
|
||||
|
||||
async tagImage(thumbnailPath: string) {
|
||||
try {
|
||||
const isExist = fs.existsSync(thumbnailPath);
|
||||
if (isExist) {
|
||||
const tags = [];
|
||||
const image = fs.readFileSync(thumbnailPath);
|
||||
const decodedImage = tf.node.decodeImage(image, 3) as tf.Tensor3D;
|
||||
const predictions = await this.mobileNetModel.classify(decodedImage);
|
||||
|
||||
for (const prediction of predictions) {
|
||||
if (prediction.probability >= 0.1) {
|
||||
tags.push(...prediction.className.split(',').map((e) => e.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
tf.dispose(decodedImage);
|
||||
return tags;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error reading file ', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
machine-learning/src/main.ts
Normal file
25
machine-learning/src/main.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
await app.listen(3001, () => {
|
||||
if (process.env.NODE_ENV == 'development') {
|
||||
Logger.log(
|
||||
'Running Immich Machine Learning in DEVELOPMENT environment',
|
||||
'IMMICH MICROSERVICES',
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV == 'production') {
|
||||
Logger.log(
|
||||
'Running Immich Machine Learning in PRODUCTION environment',
|
||||
'IMMICH MICROSERVICES',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { ObjectDetectionService } from './object-detection.service';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
@Controller('object-detection')
|
||||
export class ObjectDetectionController {
|
||||
constructor(
|
||||
private readonly objectDetectionService: ObjectDetectionService,
|
||||
) { }
|
||||
|
||||
@Post('/detect-object')
|
||||
async detectObject(@Body('thumbnailPath') thumbnailPath: string) {
|
||||
return await this.objectDetectionService.detectObject(thumbnailPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ObjectDetectionService } from './object-detection.service';
|
||||
import { ObjectDetectionController } from './object-detection.controller';
|
||||
|
||||
@Module({
|
||||
controllers: [ObjectDetectionController],
|
||||
providers: [ObjectDetectionService],
|
||||
})
|
||||
export class ObjectDetectionModule {}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import * as cocoSsd from '@tensorflow-models/coco-ssd';
|
||||
import * as tf from '@tensorflow/tfjs-node';
|
||||
import * as fs from 'fs';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectDetectionService {
|
||||
private cocoSsdModel: cocoSsd.ObjectDetection;
|
||||
|
||||
constructor() {
|
||||
Logger.log(
|
||||
`Running Node TensorFlow Version : ${tf.version['tfjs']}`,
|
||||
'ObjectDetection',
|
||||
);
|
||||
cocoSsd.load().then((model) => (this.cocoSsdModel = model));
|
||||
}
|
||||
async detectObject(thumbnailPath: string) {
|
||||
try {
|
||||
const isExist = fs.existsSync(thumbnailPath);
|
||||
if (isExist) {
|
||||
const tags = new Set();
|
||||
const image = fs.readFileSync(thumbnailPath);
|
||||
const decodedImage = tf.node.decodeImage(image, 3) as tf.Tensor3D;
|
||||
const predictions = await this.cocoSsdModel.detect(decodedImage);
|
||||
|
||||
for (const result of predictions) {
|
||||
if (result.score > 0.5) {
|
||||
tags.add(result.class);
|
||||
}
|
||||
}
|
||||
|
||||
tf.dispose(decodedImage);
|
||||
return [...tags];
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error reading file ', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user