video-transcode.processor.ts 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. import { APP_UPLOAD_LOCATION } from '@app/common/constants';
  2. import { AssetEntity } from '@app/infra';
  3. import { QueueNameEnum } from '@app/job';
  4. import { mp4ConversionProcessorName } from '@app/job/constants/job-name.constant';
  5. import { IMp4ConversionProcessor } from '@app/job/interfaces/video-transcode.interface';
  6. import { Process, Processor } from '@nestjs/bull';
  7. import { Logger } from '@nestjs/common';
  8. import { InjectRepository } from '@nestjs/typeorm';
  9. import { Job } from 'bull';
  10. import ffmpeg from 'fluent-ffmpeg';
  11. import { existsSync, mkdirSync } from 'fs';
  12. import { ImmichConfigService } from 'libs/immich-config/src';
  13. import { Repository } from 'typeorm';
  14. @Processor(QueueNameEnum.VIDEO_CONVERSION)
  15. export class VideoTranscodeProcessor {
  16. constructor(
  17. @InjectRepository(AssetEntity)
  18. private assetRepository: Repository<AssetEntity>,
  19. private immichConfigService: ImmichConfigService,
  20. ) {}
  21. @Process({ name: mp4ConversionProcessorName, concurrency: 2 })
  22. async mp4Conversion(job: Job<IMp4ConversionProcessor>) {
  23. const { asset } = job.data;
  24. if (asset.mimeType != 'video/mp4') {
  25. const basePath = APP_UPLOAD_LOCATION;
  26. const encodedVideoPath = `${basePath}/${asset.userId}/encoded-video`;
  27. if (!existsSync(encodedVideoPath)) {
  28. mkdirSync(encodedVideoPath, { recursive: true });
  29. }
  30. const savedEncodedPath = encodedVideoPath + '/' + asset.id + '.mp4';
  31. if (asset.encodedVideoPath == '' || !asset.encodedVideoPath) {
  32. // Put the processing into its own async function to prevent the job exist right away
  33. await this.runFFMPEGPipeLine(asset, savedEncodedPath);
  34. }
  35. }
  36. }
  37. async runFFMPEGPipeLine(asset: AssetEntity, savedEncodedPath: string): Promise<void> {
  38. const config = await this.immichConfigService.getConfig();
  39. return new Promise((resolve, reject) => {
  40. ffmpeg(asset.originalPath)
  41. .outputOptions([
  42. `-crf ${config.ffmpeg.crf}`,
  43. `-preset ${config.ffmpeg.preset}`,
  44. `-vcodec ${config.ffmpeg.targetVideoCodec}`,
  45. `-acodec ${config.ffmpeg.targetAudioCodec}`,
  46. `-vf scale=${config.ffmpeg.targetScaling}`,
  47. ])
  48. .output(savedEncodedPath)
  49. .on('start', () => {
  50. Logger.log('Start Converting Video', 'mp4Conversion');
  51. })
  52. .on('error', (error) => {
  53. Logger.error(`Cannot Convert Video ${error}`, 'mp4Conversion');
  54. reject();
  55. })
  56. .on('end', async () => {
  57. Logger.log(`Converting Success ${asset.id}`, 'mp4Conversion');
  58. await this.assetRepository.update({ id: asset.id }, { encodedVideoPath: savedEncodedPath });
  59. resolve();
  60. })
  61. .run();
  62. });
  63. }
  64. }