unoffical wafrn mirror
wafrn.net
atproto
social-network
activitypub
1import { logger } from './logger.js'
2
3import sharp from 'sharp'
4
5/* eslint-disable max-len */
6import fs from 'fs'
7import FfmpegCommand from 'fluent-ffmpeg'
8
9export default async function optimizeMedia(
10 inputPath: string,
11 options?: {
12 outPath?: string
13 maxSize?: number
14 keep?: boolean
15 forceImageExtension?: string
16 disableAnimation?: boolean
17 }
18): Promise<string> {
19 const fileAndExtension = options?.outPath ? [options.outPath, ''] : inputPath.split('.')
20 const originalExtension = fileAndExtension[1].toLowerCase()
21 fileAndExtension[1] = options?.forceImageExtension ? options.forceImageExtension : 'webp'
22 let outputPath = fileAndExtension.join('.')
23 const doNotDelete = options?.keep ? options.keep : false
24 switch (originalExtension) {
25 case 'pdf':
26 break
27 case 'mp4':
28 fileAndExtension[0] = fileAndExtension[0] + '_processed'
29 case 'webm':
30 case 'ogg':
31 case 'aac':
32 case 'mp3':
33 case 'wav':
34 case 'oga':
35 case 'm4a':
36 case 'mov':
37 case 'mkv':
38 case 'av1':
39 fileAndExtension[1] = 'mp4'
40 outputPath = fileAndExtension.join('.')
41 // eslint-disable-next-line no-unused-vars
42 const videoPromise = await new Promise((resolve: any, reject: any) => {
43 FfmpegCommand(inputPath).ffprobe(function (err: any, data: any) {
44 if (data) {
45 {
46 const stream = data.streams.find((stream: any) => stream.coded_height)
47 let horizontalResolution = stream ? stream.coded_width : 1280
48 let verticalResolution = stream ? stream.coded_height : 1280
49 horizontalResolution = Math.min(horizontalResolution, 1280)
50 verticalResolution = Math.min(verticalResolution, 1280)
51 const resolutionString =
52 horizontalResolution > verticalResolution ? `${horizontalResolution}x?` : `?x${verticalResolution}`
53 const videoCodec = stream.codec_name == 'h264' ? 'copy' : 'libx264'
54 const command = FfmpegCommand(inputPath)
55 if (videoCodec != 'copy') {
56 command.size(resolutionString)
57 command.videoBitrate('3500')
58 }
59 command
60 .audioCodec('aac')
61 .videoCodec(videoCodec)
62 .renice(20)
63 .save(outputPath)
64 .on('end', () => {
65 try {
66 fs.unlinkSync(inputPath)
67 logger.trace('media converted')
68 resolve()
69 } catch (exc) {
70 logger.warn(exc)
71 reject(exc)
72 }
73 })
74 }
75 } else {
76 logger.warn({
77 message: `Error on optimizemedia`,
78 error: err
79 })
80 }
81 })
82 })
83
84 break
85 default:
86 const metadata = await sharp(inputPath).metadata()
87 if (!options?.outPath) {
88 fileAndExtension[0] = fileAndExtension[0] + '_processed'
89 outputPath = fileAndExtension.join('.')
90 }
91 if (metadata.delay && !(options?.forceImageExtension && options.forceImageExtension == 'png')) {
92 fileAndExtension[1] = 'webp'
93 outputPath = fileAndExtension.join('.')
94 }
95
96 let conversion = sharp(inputPath, { animated: !options?.disableAnimation, failOnError: false }).rotate()
97 if (options?.maxSize) {
98 const imageMetadata = await conversion.metadata()
99 if (
100 imageMetadata.height &&
101 imageMetadata.width &&
102 (imageMetadata.height > options.maxSize || imageMetadata.width > options.maxSize)
103 ) {
104 let height = imageMetadata.delay ? imageMetadata.height / imageMetadata.delay.length : imageMetadata.height
105 let width = imageMetadata.width
106 const maxSize = options.maxSize
107 if (height > width) {
108 height = Math.round((maxSize * width) / height)
109 width = maxSize
110 } else {
111 width = Math.round((maxSize * height) / width)
112 height = maxSize
113 }
114
115 conversion.resize(height, width)
116 }
117 }
118 if (fileAndExtension[1] == 'webp') {
119 let stat = await fs.promises.stat(inputPath)
120 let lossless = false
121 // if the input is PNG we probably want the output to be lossless too
122 // also allow GIFs under 2MB to be kept as lossless
123 // (smaller GIFs are likely to be something like pixel art
124 // where we want to keep fine detail)
125 const lower = inputPath.toLowerCase()
126 if (lower.endsWith('png') || (lower.endsWith('gif') && stat.size <= 1024 ** 2 * 2)) {
127 lossless = true
128 }
129 conversion.webp({
130 lossless: lossless
131 })
132 }
133 await conversion.toFile(outputPath)
134 if (!doNotDelete) {
135 fs.unlinkSync(inputPath)
136 }
137 }
138 return outputPath
139}