unoffical wafrn mirror wafrn.net
atproto social-network activitypub
at testPDSNotExplode 139 lines 4.9 kB view raw
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}