From 5b4ac86f4d1df123b38be51d1c20397c97016ae0 Mon Sep 17 00:00:00 2001 From: Ashvin Jaiswal Date: Fri, 6 Sep 2024 14:25:04 +0100 Subject: [PATCH] Add video compression using the ffmpeg used by Cypress internally (#205) * Add video compression using the ffmpeg used by cypress internally * Add video compression option to agent --- lib/reporter.js | 4 +- lib/utils/attachments.js | 34 +++++++ package-lock.json | 211 +++++++++++++++++++++++++++++++++++++-- package.json | 4 + 4 files changed, 245 insertions(+), 8 deletions(-) diff --git a/lib/reporter.js b/lib/reporter.js index 321dffb..b9d5c64 100644 --- a/lib/reporter.js +++ b/lib/reporter.js @@ -190,10 +190,12 @@ class Reporter { } async sendVideo(suiteInfo) { - const { waitForVideoTimeout, waitForVideoInterval, videosFolder } = this.config; + const { waitForVideoTimeout, waitForVideoInterval, videosFolder, videoCompression } = + this.config; const { testFileName, tempId, title } = suiteInfo; const file = await getVideoFile( testFileName, + videoCompression, videosFolder, waitForVideoTimeout, waitForVideoInterval, diff --git a/lib/utils/attachments.js b/lib/utils/attachments.js index e68afbf..52fadaf 100644 --- a/lib/utils/attachments.js +++ b/lib/utils/attachments.js @@ -17,11 +17,19 @@ const fs = require('fs'); const glob = require('glob'); const path = require('path'); +const ffmpeg = require('fluent-ffmpeg'); +const ffmpegInstaller = require('@ffmpeg-installer/ffmpeg'); +const ffprobeStatic = require('ffprobe-static'); + +ffmpeg.setFfmpegPath(ffmpegInstaller.path); +ffmpeg.setFfprobePath(ffprobeStatic.path); const fsPromises = fs.promises; const DEFAULT_WAIT_FOR_FILE_TIMEOUT = 10000; const DEFAULT_WAIT_FOR_FILE_INTERVAL = 500; +const DEFAULT_CRF = 32; +const MAX_CRF = 52; const getScreenshotAttachment = async (absolutePath) => { if (!absolutePath) return absolutePath; @@ -96,8 +104,28 @@ const waitForVideoFile = ( checkFileExistsAndReady().catch(reject); }); +const compressVideo = (filePath, crfValue) => { + return new Promise((resolve, reject) => { + const outputFilePath = path.join( + path.dirname(filePath), + `compressed_${path.basename(filePath)}`, + ); + + ffmpeg(filePath) + .outputOptions(`-crf ${crfValue}`) + .save(outputFilePath) + .on('end', () => { + resolve(outputFilePath); + }) + .on('error', (err) => { + reject(err); + }); + }); +}; + const getVideoFile = async ( specFileName, + videoCompression, videosFolder = '**', timeout = DEFAULT_WAIT_FOR_FILE_TIMEOUT, interval = DEFAULT_WAIT_FOR_FILE_INTERVAL, @@ -113,6 +141,12 @@ const getVideoFile = async ( try { videoFilePath = await waitForVideoFile(globFilePath, timeout, interval); + + if (typeof videoCompression === 'boolean' && videoCompression) { + videoFilePath = await compressVideo(videoFilePath, DEFAULT_CRF); + } else if (typeof videoCompression === 'number' && videoCompression < MAX_CRF) { + videoFilePath = await compressVideo(videoFilePath, videoCompression); + } } catch (e) { console.warn(e.message); return null; diff --git a/package-lock.json b/package-lock.json index 36ece26..1768db3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@reportportal/agent-js-cypress", - "version": "5.3.2", + "version": "5.3.4", "license": "Apache-2.0", "dependencies": { "@reportportal/client-javascript": "~5.1.4", @@ -16,6 +16,7 @@ "node-ipc": "9.1.1" }, "devDependencies": { + "@ffmpeg-installer/ffmpeg": "^1.1.0", "@types/jest": "^29.5.12", "cypress": "^13.13.0", "eslint": "^8.57.0", @@ -25,6 +26,9 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^23.20.0", "eslint-plugin-prettier": "^4.2.1", + "ffmpeg": "^0.0.4", + "ffprobe-static": "^3.1.0", + "fluent-ffmpeg": "^2.1.3", "jest": "^29.7.0", "mock-fs": "^4.14.0", "prettier": "^2.8.8" @@ -766,6 +770,141 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@ffmpeg-installer/darwin-arm64": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-arm64/-/darwin-arm64-4.1.5.tgz", + "integrity": "sha512-hYqTiP63mXz7wSQfuqfFwfLOfwwFChUedeCVKkBtl/cliaTM7/ePI9bVzfZ2c+dWu3TqCwLDRWNSJ5pqZl8otA==", + "cpu": [ + "arm64" + ], + "dev": true, + "hasInstallScript": true, + "license": "https://git.ffmpeg.org/gitweb/ffmpeg.git/blob_plain/HEAD:/LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ffmpeg-installer/darwin-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/darwin-x64/-/darwin-x64-4.1.0.tgz", + "integrity": "sha512-Z4EyG3cIFjdhlY8wI9aLUXuH8nVt7E9SlMVZtWvSPnm2sm37/yC2CwjUzyCQbJbySnef1tQwGG2Sx+uWhd9IAw==", + "cpu": [ + "x64" + ], + "dev": true, + "hasInstallScript": true, + "license": "LGPL-2.1", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@ffmpeg-installer/ffmpeg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/ffmpeg/-/ffmpeg-1.1.0.tgz", + "integrity": "sha512-Uq4rmwkdGxIa9A6Bd/VqqYbT7zqh1GrT5/rFwCwKM70b42W5gIjWeVETq6SdcL0zXqDtY081Ws/iJWhr1+xvQg==", + "dev": true, + "license": "LGPL-2.1", + "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" + } + }, + "node_modules/@ffmpeg-installer/linux-arm": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm/-/linux-arm-4.1.3.tgz", + "integrity": "sha512-NDf5V6l8AfzZ8WzUGZ5mV8O/xMzRag2ETR6+TlGIsMHp81agx51cqpPItXPib/nAZYmo55Bl2L6/WOMI3A5YRg==", + "cpu": [ + "arm" + ], + "dev": true, + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-arm64": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-arm64/-/linux-arm64-4.1.4.tgz", + "integrity": "sha512-dljEqAOD0oIM6O6DxBW9US/FkvqvQwgJ2lGHOwHDDwu/pX8+V0YsDL1xqHbj1DMX/+nP9rxw7G7gcUvGspSoKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-ia32": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-ia32/-/linux-ia32-4.1.0.tgz", + "integrity": "sha512-0LWyFQnPf+Ij9GQGD034hS6A90URNu9HCtQ5cTqo5MxOEc7Rd8gLXrJvn++UmxhU0J5RyRE9KRYstdCVUjkNOQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/linux-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/linux-x64/-/linux-x64-4.1.0.tgz", + "integrity": "sha512-Y5BWhGLU/WpQjOArNIgXD3z5mxxdV8c41C+U15nsE5yF8tVcdCGet5zPs5Zy3Ta6bU7haGpIzryutqCGQA/W8A==", + "cpu": [ + "x64" + ], + "dev": true, + "hasInstallScript": true, + "license": "GPLv3", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@ffmpeg-installer/win32-ia32": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-ia32/-/win32-ia32-4.1.0.tgz", + "integrity": "sha512-FV2D7RlaZv/lrtdhaQ4oETwoFUsUjlUiasiZLDxhEUPdNDWcH1OU9K1xTvqz+OXLdsmYelUDuBS/zkMOTtlUAw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "GPLv3", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@ffmpeg-installer/win32-x64": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ffmpeg-installer/win32-x64/-/win32-x64-4.1.0.tgz", + "integrity": "sha512-Drt5u2vzDnIONf4ZEkKtFlbvwj6rI3kxw1Ck9fpudmtgaZIHD4ucsWB2lCZBXRxJgXR+2IMSti+4rtM4C4rXgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "GPLv3", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -1958,9 +2097,10 @@ "dev": true }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -3648,6 +3788,22 @@ "pend": "~1.2.0" } }, + "node_modules/ffmpeg": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/ffmpeg/-/ffmpeg-0.0.4.tgz", + "integrity": "sha512-3TgWUJJlZGQn+crJFyhsO/oNeRRnGTy6GhgS98oUCIfZrOW5haPPV7DUfOm3xJcHr5q3TJpjk2GudPutrNisRA==", + "dev": true, + "dependencies": { + "when": ">= 0.0.1" + } + }, + "node_modules/ffprobe-static": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ffprobe-static/-/ffprobe-static-3.1.0.tgz", + "integrity": "sha512-Dvpa9uhVMOYivhHKWLGDoa512J751qN1WZAIO+Xw4L/mrUSPxS4DApzSUDbCFE/LUq2+xYnznEahTd63AqBSpA==", + "dev": true, + "license": "MIT" + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -3738,6 +3894,39 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/fluent-ffmpeg": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz", + "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^0.2.9", + "which": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/fluent-ffmpeg/node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==", + "dev": true + }, + "node_modules/fluent-ffmpeg/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/follow-redirects": { "version": "1.15.6", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", @@ -5737,10 +5926,11 @@ "dev": true }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -7661,6 +7851,13 @@ "makeerror": "1.0.12" } }, + "node_modules/when": { + "version": "3.7.8", + "resolved": "https://registry.npmjs.org/when/-/when-3.7.8.tgz", + "integrity": "sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==", + "dev": true, + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index c92d720..23664e1 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,10 @@ "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^23.20.0", "eslint-plugin-prettier": "^4.2.1", + "@ffmpeg-installer/ffmpeg": "^1.1.0", + "ffmpeg": "^0.0.4", + "ffprobe-static": "^3.1.0", + "fluent-ffmpeg": "^2.1.3", "jest": "^29.7.0", "mock-fs": "^4.14.0", "prettier": "^2.8.8"