mirror of
https://github.com/immich-app/immich.git
synced 2026-01-05 01:27:18 -08:00
* feat(server/web): Initial support for RAF and SRW RAW formats.
* It should return the promise.
* Better comment
* feat(server/web): file-uploader needed changes.
* Remove un-used imports
* The failing test.. is no longer failing.
* Run prettier
* Original implementation with just a catch block added.
* feat(server): Some tests and specific handling for the two raw formats
* feat(web): Helper for raw image type.
* Handling of mimetype on server
* Handling of mimetypes on web with a map
* Bring back the acceptedfile filter
* Fix the asset-upload tests after changes
* acceptedFile is not usable due to type being empty from browser.
* Switch needs to use lowercase variants.
* Address Discord comments
* feat(mobile): Library page rework (album sorting, favorites) (#1501)
* Add album sorting
* Change AppBar to match photos page behaviour
* Add buttons
* First crude implementation of the favorites page
* Clean up
* Add favorite button
* i18n
* Add star indicator to thumbnail
* Add favorite logic to separate provider and fix favorite behavior in album
* Review feedback (Add isFavorite variable)
* dev: style buttons
* dev: styled drop down button
---------
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
* feat(mobile): Tap to enter immersive mode on gallery viewer (#1546)
* feat(mobile): Removed stay logged in checkbox and made it enabled by default (#1550)
* removed stay logged in checkbox and made it enabled by default
* adds padding to login button
* removed all isSaveLogin
* fix: logout would re-login with previous credential upon app restart
---------
Co-authored-by: Alex <alex.tran1502@gmail.com>
* chore(server): remove token when logged out (#1560)
* chore(mobile): invoke logout() on mobile app
* feat: add mechanism to delete token from logging out endpoint
* fix: set state after login sequence success
* fix: not removing token when logging out from OAuth
* fix: prettier
* refactor: using accessTokenId to delete
* chore: pr comments
* fix: test
* fix: test threshold
* feat(deployment): support docker secrets (#1254)
* Support secrets
* Rewrite to support sh
* Remove JWT_SECRET
* fix(mobile): Added flutter native splash and splash screens (#1520)
* rebasing
* added launch background image to repository
---------
Co-authored-by: Marty Fuhry <marty@fuhry.farm>
* refactor(mobile): introduce Album & User classes (#1561)
replace usages of AlbumResponseDto with Album
replace usages of UserResponseDto with User
* feat(mobile): Multiselect add to favorite from the timeline (#1558)
* multiselect add to favorites
* feat(server): add updatedAt to Asset, Album and User (#1566)
* feat: add updatedAt info to DTO and generate api
* chore: remove unsued file
* chore: Add update statement to add/remove asset/user to album
* fix: test
* chore(server): update package-lock.json to match package.json (#1573)
* chore(server) Add user FK to album entity (#1569)
* chore(deps): bump docker/setup-buildx-action from 2.4.0 to 2.4.1 (#1575)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.4.0...v2.4.1)
---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(server): make owner as required response for AlbumResponseDto (#1579)
* feat(GitHub): update bug and feature request template (#1584)
* dev: Reusing template from Home Assistant
* dev: add bug report template
* fix: template
* dev: change type
* dev:
* dev: add default labels
* dev: Add default title
* dev: add feature request template
* remove feature request from markdown
* dev: frontmatter
* fix(GitHub): feature request template
* fix(GitHub): feature request form has wrong type for textarea
* feat(mobile): Responsive layout improvements with a navigation rail and album grid (#1583)
* feat(proxy): Initial IPv6 support (#1577)
* fix(server): Create album response doesn't have owner property as required (#1704)
* feat(web): allow uploading more file types (#1570)
* feat(web): allow uploading more file types
* fix(web): make filename extension lowercase
* refactor(mobile): add Isar DB & Store class (#1574)
* refactor(mobile): add Isar DB & Store class
new Store: globally accessible key-value store like Hive (but based on Isar)
replace first few places of Hive usage with the new Store
* reduce max. DB size to prevent errors on older iOS devices
---------
Co-authored-by: Alex <alex.tran1502@gmail.com>
* feat(mobile): Home screen customization options (#1563)
* Try staggered layout for home page
* Introduce setting for dynamic layout
* Fix some provider related bugs
* Make asset grouping configurable
* Add translation keys, refactor group title
* Rename enum values
* Fix enum names
* Reformat long if statement
* Fix timezone related bug
* Minor clean up
* Fix unit test
* Add second assets check back to home screen
* [Localizely] Translations update (#1707)
* fix(server): get shared link album info doesn't contain owner property (#1708)
* Version v1.46.0
* feat(server/web): file-uploader needed changes.
* Add raf and srw to the file names.
* Remember to add the extensions to fileSelector.
* Removed the getMimeType function on server as shouldn't be needed anymore.
* Revert "Removed the getMimeType function on server as shouldn't be needed anymore."
It is required still.
This reverts commit fc766dd0be.
* Should use proper mimetypes.
* fix linter
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Matthias Rupp <matthias.rupp@posteo.de>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: martyfuhry <martyfuhry@gmail.com>
Co-authored-by: James <jdm12989@gmail.com>
Co-authored-by: Marty Fuhry <marty@fuhry.farm>
Co-authored-by: Fynn Petersen-Frey <zoodyy@users.noreply.github.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Michel Heusschen <59014050+michelheusschen@users.noreply.github.com>
Co-authored-by: Immich Release Bot <bot@immich.app>
131 lines
5.0 KiB
TypeScript
131 lines
5.0 KiB
TypeScript
import { APP_UPLOAD_LOCATION } from '@app/common';
|
|
import { AssetEntity, AssetType } from '@app/infra';
|
|
import { WebpGeneratorProcessor, JpegGeneratorProcessor, QueueName, JobName } from '@app/domain';
|
|
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
|
import { Logger } from '@nestjs/common';
|
|
import { InjectRepository } from '@nestjs/typeorm';
|
|
import { mapAsset } from '@app/domain';
|
|
import { Job, Queue } from 'bull';
|
|
import ffmpeg from 'fluent-ffmpeg';
|
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
import sanitize from 'sanitize-filename';
|
|
import sharp from 'sharp';
|
|
import { Repository } from 'typeorm/repository/Repository';
|
|
import { join } from 'path';
|
|
import { CommunicationGateway } from 'apps/immich/src/api-v1/communication/communication.gateway';
|
|
import { IMachineLearningJob } from '@app/domain';
|
|
import { exiftool } from 'exiftool-vendored';
|
|
|
|
@Processor(QueueName.THUMBNAIL_GENERATION)
|
|
export class ThumbnailGeneratorProcessor {
|
|
readonly logger: Logger = new Logger(ThumbnailGeneratorProcessor.name);
|
|
|
|
constructor(
|
|
@InjectRepository(AssetEntity)
|
|
private assetRepository: Repository<AssetEntity>,
|
|
|
|
@InjectQueue(QueueName.THUMBNAIL_GENERATION)
|
|
private thumbnailGeneratorQueue: Queue,
|
|
|
|
private wsCommunicationGateway: CommunicationGateway,
|
|
|
|
@InjectQueue(QueueName.MACHINE_LEARNING)
|
|
private machineLearningQueue: Queue<IMachineLearningJob>,
|
|
) {}
|
|
|
|
@Process({ name: JobName.GENERATE_JPEG_THUMBNAIL, concurrency: 3 })
|
|
async generateJPEGThumbnail(job: Job<JpegGeneratorProcessor>) {
|
|
const basePath = APP_UPLOAD_LOCATION;
|
|
|
|
const { asset } = job.data;
|
|
const sanitizedDeviceId = sanitize(String(asset.deviceId));
|
|
|
|
const resizePath = join(basePath, asset.userId, 'thumb', sanitizedDeviceId);
|
|
|
|
if (!existsSync(resizePath)) {
|
|
mkdirSync(resizePath, { recursive: true });
|
|
}
|
|
|
|
const jpegThumbnailPath = join(resizePath, `${asset.id}.jpeg`);
|
|
|
|
if (asset.type == AssetType.IMAGE) {
|
|
try {
|
|
await sharp(asset.originalPath, { failOnError: false })
|
|
.resize(1440, 1440, { fit: 'outside', withoutEnlargement: true })
|
|
.jpeg()
|
|
.rotate()
|
|
.toFile(jpegThumbnailPath)
|
|
.catch(() => {
|
|
this.logger.warn(
|
|
'Failed to generate jpeg thumbnail for asset: ' +
|
|
asset.id +
|
|
' using sharp, failing over to exiftool-vendored',
|
|
);
|
|
return exiftool.extractThumbnail(asset.originalPath, jpegThumbnailPath);
|
|
});
|
|
await this.assetRepository.update({ id: asset.id }, { resizePath: jpegThumbnailPath });
|
|
} catch (error: any) {
|
|
this.logger.error('Failed to generate jpeg thumbnail for asset: ' + asset.id, error.stack);
|
|
}
|
|
|
|
// Update resize path to send to generate webp queue
|
|
asset.resizePath = jpegThumbnailPath;
|
|
|
|
await this.thumbnailGeneratorQueue.add(JobName.GENERATE_WEBP_THUMBNAIL, { asset });
|
|
await this.machineLearningQueue.add(JobName.IMAGE_TAGGING, { asset });
|
|
await this.machineLearningQueue.add(JobName.OBJECT_DETECTION, { asset });
|
|
|
|
this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset)));
|
|
}
|
|
|
|
if (asset.type == AssetType.VIDEO) {
|
|
await new Promise((resolve, reject) => {
|
|
ffmpeg(asset.originalPath)
|
|
.outputOptions(['-ss 00:00:00.000', '-frames:v 1'])
|
|
.output(jpegThumbnailPath)
|
|
.on('start', () => {
|
|
Logger.log('Start Generating Video Thumbnail', 'generateJPEGThumbnail');
|
|
})
|
|
.on('error', (error) => {
|
|
Logger.error(`Cannot Generate Video Thumbnail ${error}`, 'generateJPEGThumbnail');
|
|
reject(error);
|
|
})
|
|
.on('end', async () => {
|
|
Logger.log(`Generating Video Thumbnail Success ${asset.id}`, 'generateJPEGThumbnail');
|
|
resolve(asset);
|
|
})
|
|
.run();
|
|
});
|
|
|
|
await this.assetRepository.update({ id: asset.id }, { resizePath: jpegThumbnailPath });
|
|
|
|
// Update resize path to send to generate webp queue
|
|
asset.resizePath = jpegThumbnailPath;
|
|
|
|
await this.thumbnailGeneratorQueue.add(JobName.GENERATE_WEBP_THUMBNAIL, { asset });
|
|
await this.machineLearningQueue.add(JobName.IMAGE_TAGGING, { asset });
|
|
await this.machineLearningQueue.add(JobName.OBJECT_DETECTION, { asset });
|
|
|
|
this.wsCommunicationGateway.server.to(asset.userId).emit('on_upload_success', JSON.stringify(mapAsset(asset)));
|
|
}
|
|
}
|
|
|
|
@Process({ name: JobName.GENERATE_WEBP_THUMBNAIL, concurrency: 3 })
|
|
async generateWepbThumbnail(job: Job<WebpGeneratorProcessor>) {
|
|
const { asset } = job.data;
|
|
|
|
if (!asset.resizePath) {
|
|
return;
|
|
}
|
|
|
|
const webpPath = asset.resizePath.replace('jpeg', 'webp');
|
|
|
|
try {
|
|
await sharp(asset.resizePath, { failOnError: false }).resize(250).webp().rotate().toFile(webpPath);
|
|
await this.assetRepository.update({ id: asset.id }, { webpPath: webpPath });
|
|
} catch (error: any) {
|
|
this.logger.error('Failed to generate webp thumbnail for asset: ' + asset.id, error.stack);
|
|
}
|
|
}
|
|
}
|