feat(web,server): add thumbhash support (#2649)

* add thumbhash: server generation and web impl

* move logic to infra & use byta in db

* remove unnecesary logs

* update generated API and simplify thumbhash gen

* fix check errors

* removed unnecessary library and css tag

* style edits

* syntax mistake

* update server test, change thumbhash job name

* fix tests

* Update server/src/domain/asset/response-dto/asset-response.dto.ts

Co-authored-by: Thomas <9749173+uhthomas@users.noreply.github.com>

* add unit test, change migration date

* change to official thumbhash impl

* update call method to not use eval

* "generate missing" looks for thumbhash

* improve queue & improve syntax

* update syntax again

* update tests

* fix thumbhash generation

* consolidate queueing to avoid duplication

* cover all types of incorrect thumbnail cases

* split out jest tasks

* put back thumbnail duration loading for images without thumbhash

* Remove stray package.json

---------

Co-authored-by: Luke McCarthy <mail@lukehmcc.com>
Co-authored-by: Thomas <9749173+uhthomas@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
Covalent
2023-06-17 23:22:31 -04:00
committed by GitHub
parent 3512140148
commit 3e804f16df
29 changed files with 354 additions and 29 deletions

View File

@@ -54,9 +54,9 @@ describe(MediaService.name, () => {
});
});
it('should queue all assets with missing thumbnails', async () => {
it('should queue all assets with missing resize path', async () => {
assetMock.getWithout.mockResolvedValue({
items: [assetEntityStub.image],
items: [assetEntityStub.noResizePath],
hasNextPage: false,
});
@@ -69,6 +69,38 @@ describe(MediaService.name, () => {
data: { id: assetEntityStub.image.id },
});
});
it('should queue all assets with missing webp path', async () => {
assetMock.getWithout.mockResolvedValue({
items: [assetEntityStub.noWebpPath],
hasNextPage: false,
});
await sut.handleQueueGenerateThumbnails({ force: false });
expect(assetMock.getAll).not.toHaveBeenCalled();
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL);
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.GENERATE_WEBP_THUMBNAIL,
data: { id: assetEntityStub.image.id },
});
});
it('should queue all assets with missing thumbhash', async () => {
assetMock.getWithout.mockResolvedValue({
items: [assetEntityStub.noThumbhash],
hasNextPage: false,
});
await sut.handleQueueGenerateThumbnails({ force: false });
expect(assetMock.getAll).not.toHaveBeenCalled();
expect(assetMock.getWithout).toHaveBeenCalledWith({ skip: 0, take: 1000 }, WithoutProperty.THUMBNAIL);
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.GENERATE_THUMBHASH_THUMBNAIL,
data: { id: assetEntityStub.image.id },
});
});
});
describe('handleGenerateJpegThumbnail', () => {
@@ -129,6 +161,25 @@ describe(MediaService.name, () => {
});
});
describe('handleGenerateThumbhashThumbnail', () => {
it('should skip thumbhash generation if resize path is missing', async () => {
assetMock.getByIds.mockResolvedValue([assetEntityStub.noResizePath]);
await sut.handleGenerateThumbhashThumbnail({ id: assetEntityStub.noResizePath.id });
expect(mediaMock.generateThumbhash).not.toHaveBeenCalled();
});
it('should generate a thumbhash', async () => {
const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8');
assetMock.getByIds.mockResolvedValue([assetEntityStub.image]);
mediaMock.generateThumbhash.mockResolvedValue(thumbhashBuffer);
await sut.handleGenerateThumbhashThumbnail({ id: assetEntityStub.image.id });
expect(mediaMock.generateThumbhash).toHaveBeenCalledWith('/uploads/user-id/thumbs/path.ext');
expect(assetMock.save).toHaveBeenCalledWith({ id: 'asset-id', thumbhash: thumbhashBuffer });
});
});
describe('handleQueueVideoConversion', () => {
it('should queue all video assets', async () => {
assetMock.getAll.mockResolvedValue({