From fa3d524d1d446cd26e2d2c0e252333439d787544 Mon Sep 17 00:00:00 2001 From: Matthias Van Parijs Date: Mon, 16 Sep 2024 18:41:28 +0200 Subject: [PATCH] feat: HEVC support --- packages/artisan/package.json | 5 +- .../artisan/src/consumer/workers/ffmpeg.ts | 199 ++++++++++------ packages/artisan/src/schemas.ts | 2 +- pnpm-lock.yaml | 219 +++++++++--------- 4 files changed, 235 insertions(+), 190 deletions(-) diff --git a/packages/artisan/package.json b/packages/artisan/package.json index 8ac94375..85e2b1f3 100644 --- a/packages/artisan/package.json +++ b/packages/artisan/package.json @@ -22,7 +22,6 @@ }, "devDependencies": { "@types/find-config": "^1.0.4", - "@types/fluent-ffmpeg": "^2.1.25", "@types/mime-types": "^2.1.4", "@types/node": "^22.1.0", "@types/parse-filepath": "^1.0.2", @@ -32,11 +31,11 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.623.0", - "@ffmpeg-installer/ffmpeg": "^1.1.0", "bullmq": "^5.12.0", "dotenv": "^16.4.5", + "ffmpeg-static": "^5.2.0", + "ffmpeggy": "^3.0.1", "find-config": "^1.0.0", - "fluent-ffmpeg": "^2.1.3", "glob": "^11.0.0", "iso-language-codes": "^2.0.0", "mime-types": "^2.1.35", diff --git a/packages/artisan/src/consumer/workers/ffmpeg.ts b/packages/artisan/src/consumer/workers/ffmpeg.ts index ecd9d479..3390a2c9 100644 --- a/packages/artisan/src/consumer/workers/ffmpeg.ts +++ b/packages/artisan/src/consumer/workers/ffmpeg.ts @@ -1,15 +1,19 @@ import { dirSync } from "tmp"; -import ffmpeg from "fluent-ffmpeg"; import { downloadFile, uploadFile } from "../s3.js"; import parseFilePath from "parse-filepath"; -import ffmpegBin from "@ffmpeg-installer/ffmpeg"; +import { FFmpeggy } from "ffmpeggy"; +import ffmpegBin from "ffmpeg-static"; import type { Job } from "bullmq"; import type { Stream, Input } from "../../schemas.js"; -import type { FfmpegCommand } from "fluent-ffmpeg"; -console.log(`Set ffmpeg path to "${ffmpegBin.path}"`); +if (!ffmpegBin) { + throw new Error("Cannot find ffmpeg bin"); +} -ffmpeg.setFfmpegPath(ffmpegBin.path); +FFmpeggy.DefaultConfig = { + ...FFmpeggy.DefaultConfig, + ffmpegBin, +}; export type FfmpegData = { params: { @@ -28,103 +32,85 @@ export type FfmpegResult = { stream: Stream; }; -export default async function (job: Job) { - const { params } = job.data; - - const dir = dirSync(); - let inputFile = parseFilePath(params.input.path); +async function prepareInput(input: Input) { + const filePath = parseFilePath(input.path); - if (inputFile.dir.startsWith("s3://")) { - const s3SourcePath = inputFile.path.replace("s3://", ""); + if (filePath.dir.startsWith("s3://")) { + // If the input is on S3, download the file locally. + const dir = dirSync(); - job.log(`Source is on s3, downloading ${s3SourcePath} to ${dir.name}`); + const s3SourcePath = filePath.path.replace("s3://", ""); await downloadFile(dir.name, s3SourcePath); - inputFile = parseFilePath(`${dir.name}/${inputFile.basename}`); + + return parseFilePath(`${dir.name}/${filePath.basename}`); } + return filePath; +} + +export default async function (job: Job) { + const { params } = job.data; + + const dir = dirSync(); + + const inputFile = await prepareInput(params.input); + job.log(`Input is ${inputFile.path}`); + const ffmpeg = new FFmpeggy({ + input: inputFile.path, + globalOptions: ["-loglevel error"], + }); + let name: string | undefined; - let ffmpegCmd: FfmpegCommand | undefined; - if (params.stream.type === "video") { - const keyFrameInterval = params.segmentSize * params.stream.framerate; - - let codec: string; - switch (params.stream.codec) { - case "h264": - codec = "libx264"; - break; - default: - codec = params.stream.codec; - break; - } + const outputOptions: string[] = []; + if (params.stream.type === "video") { name = `video_${params.stream.height}_${params.stream.bitrate}_${params.stream.codec}.m4v`; - - ffmpegCmd = ffmpeg(inputFile.path) - .noAudio() - .format("mp4") - .size(`?x${params.stream.height}`) - .aspectRatio("16:9") - .autoPad(true) - .videoCodec(codec) - .videoBitrate(params.stream.bitrate) - .outputOptions([ - `-frag_duration ${params.segmentSize * 1e6}`, - "-movflags +frag_keyframe", - `-r ${params.stream.framerate}`, - `-keyint_min ${keyFrameInterval}`, - `-g ${keyFrameInterval}`, - ]) - .output(`${dir.name}/${name}`); + outputOptions.push( + ...getVideoOutputOptions(params.stream, params.segmentSize), + ); } if (params.stream.type === "audio") { name = `audio_${params.stream.language}_${params.stream.bitrate}.m4a`; - - ffmpegCmd = ffmpeg(inputFile.path) - .noVideo() - .format("mp4") - .audioCodec(params.stream.codec) - .audioBitrate(params.stream.bitrate) - .outputOptions([ - `-metadata language=${params.stream.language}`, - `-frag_duration ${params.segmentSize * 1e6}`, - ]) - .output(`${dir.name}/${name}`); + outputOptions.push( + ...getAudioOutputOptions(params.stream, params.segmentSize), + ); } if (params.stream.type === "text") { name = `text_${params.stream.language}.vtt`; - - ffmpegCmd = ffmpeg(inputFile.path).output(`${dir.name}/${name}`); + outputOptions.push(...getTextOutputOptions(params.stream)); } - if (!ffmpegCmd || !name) { - throw new Error("No ffmpeg cmd or file created."); + if (!name) { + throw new Error( + "Missing name, this is most likely a bug. Report it, please.", + ); } + ffmpeg.setOutput(`${dir.name}/${name}`); + ffmpeg.setOutputOptions(outputOptions); + job.log(`Transcode to ${name}`); - await new Promise((resolve, reject) => { - ffmpegCmd - .on("error", reject) - .on("end", () => { - job.updateProgress(100); - job.log("Finished transcode"); - resolve(undefined); - }) - .on("start", (cmdLine) => { - job.log(cmdLine); - }) - .on("progress", (event) => { - job.updateProgress(event.percent ?? 0); - }) - .run(); + ffmpeg.on("start", (args) => { + job.log(args.join(" ")); + }); + + ffmpeg.on("progress", (event) => { + job.updateProgress(event.percent ?? 0); }); + ffmpeg.run(); + + await ffmpeg.done(); + + job.updateProgress(100); + job.log( `Uploading ${dir.name}/${name} to transcode/${params.assetId}/${name}`, ); @@ -139,3 +125,68 @@ export default async function (job: Job) { stream: params.stream, }; } + +function getVideoOutputOptions( + stream: Extract, + segmentSize: number, +) { + const args: string[] = [ + "-f mp4", + "-an", + `-c:v ${stream.codec}`, + `-b:v ${stream.bitrate}`, + `-r ${stream.framerate}`, + "-movflags +frag_keyframe", + `-frag_duration ${segmentSize * 1_000_000}`, + ]; + + if (stream.codec === "h264") { + let profile = "main"; + if (stream.height >= 720) { + profile = "high"; + } + args.push(`-profile:v ${profile}`); + } + + if (stream.codec === "h264" || stream.codec === "hevc") { + args.push( + "-preset slow", + "-flags +loop", + "-pix_fmt yuv420p", + "-flags +cgop", + ); + } + + const filters: string[] = ["setsar=1:1", `scale=-2:${stream.height}`]; + + args.push(`-vf ${filters.join(",")}`); + + const keyFrameRate = segmentSize * stream.framerate; + args.push(`-keyint_min ${keyFrameRate}`, `-g ${keyFrameRate}`); + + return args; +} + +function getAudioOutputOptions( + stream: Extract, + segmentSize: number, +) { + const args: string[] = [ + "-f mp4", + "-vn", + "-ac 2", + `-c:a ${stream.codec}`, + `-b:a ${stream.bitrate}`, + `-frag_duration ${segmentSize * 1_000_000}`, + `-metadata language=${stream.language}`, + "-strict experimental", + ]; + + return args; +} + +function getTextOutputOptions(stream: Extract) { + const args: string[] = ["-f webvtt"]; + + return args; +} diff --git a/packages/artisan/src/schemas.ts b/packages/artisan/src/schemas.ts index 92309856..c42fe7a6 100644 --- a/packages/artisan/src/schemas.ts +++ b/packages/artisan/src/schemas.ts @@ -4,7 +4,7 @@ import { zodEnumLanguage } from "./lib/zod-helpers.js"; export const streamSchema = z.discriminatedUnion("type", [ z.object({ type: z.literal("video"), - codec: z.enum(["h264", "vp9"]), + codec: z.enum(["h264", "vp9", "hevc"]), height: z.number(), bitrate: z.number(), framerate: z.number(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 643059f0..ebd7b562 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -87,21 +87,21 @@ importers: '@aws-sdk/client-s3': specifier: ^3.623.0 version: 3.623.0 - '@ffmpeg-installer/ffmpeg': - specifier: ^1.1.0 - version: 1.1.0 bullmq: specifier: ^5.12.0 version: 5.12.0 dotenv: specifier: ^16.4.5 version: 16.4.5 + ffmpeg-static: + specifier: ^5.2.0 + version: 5.2.0 + ffmpeggy: + specifier: ^3.0.1 + version: 3.0.1 find-config: specifier: ^1.0.0 version: 1.0.0 - fluent-ffmpeg: - specifier: ^2.1.3 - version: 2.1.3 glob: specifier: ^11.0.0 version: 11.0.0 @@ -130,9 +130,6 @@ importers: '@types/find-config': specifier: ^1.0.4 version: 1.0.4 - '@types/fluent-ffmpeg': - specifier: ^2.1.25 - version: 2.1.25 '@types/mime-types': specifier: ^2.1.4 version: 2.1.4 @@ -2701,6 +2698,16 @@ packages: w3c-keyname: 2.2.8 dev: false + /@derhuerst/http-basic@8.2.4: + resolution: {integrity: sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==} + engines: {node: '>=6.0.0'} + dependencies: + caseless: 0.12.0 + concat-stream: 2.0.0 + http-response-object: 3.0.2 + parse-cache-control: 1.0.1 + dev: false + /@docsearch/css@3.6.1: resolution: {integrity: sha512-VtVb5DS+0hRIprU2CO6ZQjK2Zg4QU5HrDM1+ix6rT0umsYvFvatMAnf97NHZlVWDaaLlx7GRfR/7FikANiM2Fg==} dev: true @@ -3029,83 +3036,6 @@ packages: hashlru: 2.3.0 dev: false - /@ffmpeg-installer/darwin-arm64@4.1.5: - resolution: {integrity: sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA==} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@ffmpeg-installer/darwin-x64@4.1.0: - resolution: {integrity: sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw==} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@ffmpeg-installer/ffmpeg@1.1.0: - resolution: {integrity: sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg==} - optionalDependencies: - '@ffmpeg-installer/darwin-arm64': 4.1.5 - '@ffmpeg-installer/darwin-x64': 4.1.0 - '@ffmpeg-installer/linux-arm': 4.1.3 - '@ffmpeg-installer/linux-arm64': 4.1.4 - '@ffmpeg-installer/linux-ia32': 4.1.0 - '@ffmpeg-installer/linux-x64': 4.1.0 - '@ffmpeg-installer/win32-ia32': 4.1.0 - '@ffmpeg-installer/win32-x64': 4.1.0 - dev: false - - /@ffmpeg-installer/linux-arm64@4.1.4: - resolution: {integrity: sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg==} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@ffmpeg-installer/linux-arm@4.1.3: - resolution: {integrity: sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg==} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@ffmpeg-installer/linux-ia32@4.1.0: - resolution: {integrity: sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ==} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@ffmpeg-installer/linux-x64@4.1.0: - resolution: {integrity: sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A==} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@ffmpeg-installer/win32-ia32@4.1.0: - resolution: {integrity: sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw==} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@ffmpeg-installer/win32-x64@4.1.0: - resolution: {integrity: sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg==} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - /@floating-ui/core@1.6.7: resolution: {integrity: sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==} dependencies: @@ -5908,12 +5838,6 @@ packages: resolution: {integrity: sha512-BCXaKgzHK7KnfCQBRQBWGTA+QajOE9uFolXPt+9EktiiMS56D8oXF2ZCh9eCxuEyfqDmX/mYIcmWg9j9f659eg==} dev: true - /@types/fluent-ffmpeg@2.1.25: - resolution: {integrity: sha512-a9/Jtv/RVaCG4lUwWIcuClWE5eXJFoFS/oHOecOv/RS8n+lQdJzcJVmDlxA8Xbk4B82YpO88Dijcoljb6sYTcA==} - dependencies: - '@types/node': 22.1.0 - dev: true - /@types/glob-to-regexp@0.4.4: resolution: {integrity: sha512-nDKoaKJYbnn1MZxUY0cA1bPmmgZbg0cTq7Rh13d0KWYNOiKbqoR+2d89SnRPszGh7ROzSwZ/GOjZ4jPbmmZ6Eg==} dev: true @@ -5990,6 +5914,10 @@ packages: resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} dev: false + /@types/node@10.17.60: + resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} + dev: false + /@types/node@18.19.43: resolution: {integrity: sha512-Mw/YlgXnyJdEwLoFv2dpuJaDFriX+Pc+0qOBJ57jC1H6cDxIj2xc5yUrdtArDVG0m+KV6622a4p2tenEqB3C/g==} dependencies: @@ -6442,6 +6370,15 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + /agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + dependencies: + debug: 4.3.6 + transitivePeerDependencies: + - supports-color + dev: false + /ajv-draft-04@1.0.0(ajv@8.13.0): resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} peerDependencies: @@ -6630,10 +6567,6 @@ packages: tslib: 2.6.3 dev: false - /async@0.2.10: - resolution: {integrity: sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==} - dev: false - /async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} @@ -6988,6 +6921,10 @@ packages: /caniuse-lite@1.0.30001650: resolution: {integrity: sha512-fgEc7hP/LB7iicdXHUI9VsBsMZmUmlVJeQP2qqQW+3lkqVhbmjEU8zp+h5stWeilX+G7uXuIUIIlWlDw9jdt8g==} + /caseless@0.12.0: + resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} + dev: false + /ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} dev: false @@ -7216,6 +7153,16 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + dev: false + /confbox@0.1.7: resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} @@ -7551,6 +7498,11 @@ packages: resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==} engines: {node: '>=0.12'} + /env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + dev: false + /envinfo@7.13.0: resolution: {integrity: sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==} engines: {node: '>=4'} @@ -7900,6 +7852,30 @@ packages: regexparam: 3.0.0 dev: true + /ffmpeg-static@5.2.0: + resolution: {integrity: sha512-WrM7kLW+do9HLr+H6tk7LzQ7kPqbAgLjdzNE32+u3Ff11gXt9Kkkd2nusGFrlWMIe+XaA97t+I8JS7sZIrvRgA==} + engines: {node: '>=16'} + requiresBuild: true + dependencies: + '@derhuerst/http-basic': 8.2.4 + env-paths: 2.2.1 + https-proxy-agent: 5.0.1 + progress: 2.0.3 + transitivePeerDependencies: + - supports-color + dev: false + + /ffmpeggy@3.0.1: + resolution: {integrity: sha512-OCWsSHhVyI78J/ru9XzWJaDSfLvOyNAm77F0G17xvPWa8xEqGgevjTJnfjNcfKRdIjPP+u744du5iJ5AHL9dQw==} + engines: {node: '>= 12'} + dependencies: + debug: 4.3.6 + execa: 5.1.1 + typed-emitter: 2.1.0 + transitivePeerDependencies: + - supports-color + dev: false + /filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} dependencies: @@ -7983,14 +7959,6 @@ packages: engines: {node: '>=0.4.0'} dev: false - /fluent-ffmpeg@2.1.3: - resolution: {integrity: sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==} - engines: {node: '>=18'} - dependencies: - async: 0.2.10 - which: 1.3.1 - dev: false - /focus-trap@7.5.4: resolution: {integrity: sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==} dependencies: @@ -8510,6 +8478,22 @@ packages: toidentifier: 1.0.1 dev: false + /http-response-object@3.0.2: + resolution: {integrity: sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==} + dependencies: + '@types/node': 10.17.60 + dev: false + + /https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + dependencies: + agent-base: 6.0.2 + debug: 4.3.6 + transitivePeerDependencies: + - supports-color + dev: false + /httpsnippet-lite@3.0.5: resolution: {integrity: sha512-So4qTXY5iFj5XtFDwyz2PicUu+8NWrI8e8h+ZeZoVtMNcFQp4FFIntBHUE+JPUG6QQU8o1VHCy+X4ETRDwt9CA==} engines: {node: '>=14.13'} @@ -10449,6 +10433,10 @@ packages: callsites: 3.1.0 dev: true + /parse-cache-control@1.0.1: + resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==} + dev: false + /parse-filepath@1.0.2: resolution: {integrity: sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==} engines: {node: '>=0.8'} @@ -10795,6 +10783,11 @@ packages: engines: {node: '>= 0.6.0'} dev: false + /progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + dev: false + /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -11340,7 +11333,6 @@ packages: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: tslib: 2.6.3 - dev: true /s3-sync-client@4.3.1(@aws-sdk/abort-controller@3.374.0)(@aws-sdk/client-s3@3.623.0): resolution: {integrity: sha512-nWbbKCNnXmWvD8XwdWhX25VNxIhgQEm6vXqSYjwyBNZI07OuMOr/LNOYmEPcLfqFFjy55ZNcFSBI18W29ybuUw==} @@ -12350,6 +12342,16 @@ packages: mime-types: 2.1.35 dev: false + /typed-emitter@2.1.0: + resolution: {integrity: sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==} + optionalDependencies: + rxjs: 7.8.1 + dev: false + + /typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + dev: false + /typescript@5.4.2: resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} engines: {node: '>=14.17'} @@ -12941,13 +12943,6 @@ packages: has-tostringtag: 1.0.2 dev: false - /which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: false - /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'}