ソースを参照

feat(server): enhanced thumbnails generation code (#2147)

* Add size parameter to extractVideoThumbnail

* Ensure minimum dimension of webp thumbnail
Sergey Kondrikov 2 年 前
コミット
7e526f87b4

+ 1 - 1
server/libs/domain/src/media/media.repository.ts

@@ -7,6 +7,6 @@ export interface ResizeOptions {
 
 export interface IMediaRepository {
   resize(input: string, output: string, options: ResizeOptions): Promise<void>;
-  extractVideoThumbnail(input: string, output: string): Promise<void>;
+  extractVideoThumbnail(input: string, output: string, size: number): Promise<void>;
   extractThumbnailFromExif(input: string, output: string): Promise<void>;
 }

+ 1 - 0
server/libs/domain/src/media/media.service.spec.ts

@@ -114,6 +114,7 @@ describe(MediaService.name, () => {
       expect(mediaMock.extractVideoThumbnail).toHaveBeenCalledWith(
         '/original/path.ext',
         'upload/thumbs/user-id/asset-id.jpeg',
+        1440,
       );
       expect(assetMock.save).toHaveBeenCalledWith({
         id: 'asset-id',

+ 6 - 2
server/libs/domain/src/media/media.service.ts

@@ -44,9 +44,13 @@ export class MediaService {
       this.storageRepository.mkdirSync(resizePath);
       const jpegThumbnailPath = join(resizePath, `${asset.id}.jpeg`);
 
+      const thumbnailDimension = 1440;
       if (asset.type == AssetType.IMAGE) {
         try {
-          await this.mediaRepository.resize(asset.originalPath, jpegThumbnailPath, { size: 1440, format: 'jpeg' });
+          await this.mediaRepository.resize(asset.originalPath, jpegThumbnailPath, {
+            size: thumbnailDimension,
+            format: 'jpeg',
+          });
         } catch (error) {
           this.logger.warn(
             `Failed to generate jpeg thumbnail using sharp, trying with exiftool-vendored (asset=${asset.id})`,
@@ -57,7 +61,7 @@ export class MediaService {
 
       if (asset.type == AssetType.VIDEO) {
         this.logger.log('Start Generating Video Thumbnail');
-        await this.mediaRepository.extractVideoThumbnail(asset.originalPath, jpegThumbnailPath);
+        await this.mediaRepository.extractVideoThumbnail(asset.originalPath, jpegThumbnailPath, thumbnailDimension);
         this.logger.log(`Generating Video Thumbnail Success ${asset.id}`);
       }
 

+ 11 - 3
server/libs/infra/src/repositories/media.repository.ts

@@ -11,7 +11,11 @@ export class MediaRepository implements IMediaRepository {
   async resize(input: string, output: string, options: ResizeOptions): Promise<void> {
     switch (options.format) {
       case 'webp':
-        await sharp(input, { failOnError: false }).resize(250).webp().rotate().toFile(output);
+        await sharp(input, { failOnError: false })
+          .resize(options.size, options.size, { fit: 'outside', withoutEnlargement: true })
+          .webp()
+          .rotate()
+          .toFile(output);
         return;
 
       case 'jpeg':
@@ -24,10 +28,14 @@ export class MediaRepository implements IMediaRepository {
     }
   }
 
-  extractVideoThumbnail(input: string, output: string) {
+  extractVideoThumbnail(input: string, output: string, size: number) {
     return new Promise<void>((resolve, reject) => {
       ffmpeg(input)
-        .outputOptions(['-ss 00:00:00.000', '-frames:v 1'])
+        .outputOptions([
+          '-ss 00:00:00.000',
+          '-frames:v 1',
+          `-vf scale='min(${size},iw)':'min(${size},ih)':force_original_aspect_ratio=increase`,
+        ])
         .output(output)
         .on('error', reject)
         .on('end', resolve)