From 029b6fb048225ba5ad24501ea05c0703647a6543 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 14 Jun 2023 12:51:25 +0100 Subject: [PATCH 1/6] wip: linux support --- .../src/worker/accessorHandlers/accessor.ts | 1 + .../windowsWorker/expectationHandlers/lib.ts | 70 +++++++++++++------ .../expectationHandlers/lib/scan.ts | 26 +++---- .../workers/windowsWorker/windowsWorker.ts | 3 - 4 files changed, 62 insertions(+), 38 deletions(-) diff --git a/shared/packages/worker/src/worker/accessorHandlers/accessor.ts b/shared/packages/worker/src/worker/accessorHandlers/accessor.ts index 447606f1..9460883d 100644 --- a/shared/packages/worker/src/worker/accessorHandlers/accessor.ts +++ b/shared/packages/worker/src/worker/accessorHandlers/accessor.ts @@ -35,6 +35,7 @@ export function getAccessorStaticHandle(accessor: AccessorOnPackage.Any) { } else if (accessor.type === Accessor.AccessType.HTTP_PROXY) { return HTTPProxyAccessorHandle } else if (accessor.type === Accessor.AccessType.FILE_SHARE) { + if (process.platform !== 'win32') throw new Error(`FileShareAccessor: not supported on ${process.platform}`) return FileShareAccessorHandle } else if (accessor.type === Accessor.AccessType.QUANTEL) { return QuantelAccessorHandle diff --git a/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib.ts b/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib.ts index 9c424493..36dd628c 100644 --- a/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib.ts +++ b/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib.ts @@ -367,18 +367,28 @@ export function previewFFMpegArguments(input: string, seekableSource: boolean, m return [ '-hide_banner', '-y', // Overwrite output files without asking. - seekableSource ? undefined : '-seekable 0', - `-i "${input}"`, // Input file path - '-f webm', // format: webm + seekableSource ? undefined : '-seekable', + seekableSource ? undefined : '0', + `-i`, + input, // Input file path + '-f', + 'webm', // format: webm '-an', // blocks all audio streams - '-c:v libvpx-vp9', // encoder for video (use VP9) - `-b:v ${metadata.version.bitrate || '40k'}`, - '-auto-alt-ref 1', - `-vf scale=${metadata.version.width || 320}:${metadata.version.height || -1}`, // Scale to resolution - - '-threads 1', // Number of threads to use - '-cpu-used 5', // Sacrifice quality for speed, used in combination with -deadline realtime - '-deadline realtime', // Encoder speed/quality and cpu use (best, good, realtime) + '-c:v', + 'libvpx-vp9', // encoder for video (use VP9) + `-b:v`, + `${metadata.version.bitrate || '40k'}`, + '-auto-alt-ref', + '1', + `-vf`, + `scale=${metadata.version.width || 320}:${metadata.version.height || -1}`, // Scale to resolution + + '-threads', + '1', // Number of threads to use + '-cpu-used', + '5', // Sacrifice quality for speed, used in combination with -deadline realtime + '-deadline', + 'realtime', // Encoder speed/quality and cpu use (best, good, realtime) ].filter(Boolean) as string[] // remove undefined values } @@ -397,15 +407,26 @@ export function thumbnailFFMpegArguments( ): string[] { return [ '-hide_banner', - hasVideoStream && seekTimeCode ? `-ss ${seekTimeCode}` : undefined, - `-i "${input}"`, - `-f image2`, - '-frames:v 1', - hasVideoStream - ? `-vf ${!seekTimeCode ? 'thumbnail,' : ''}scale=${metadata.version.width}:${metadata.version.height}` // Creates a thumbnail of the video. - : '-filter_complex "showwavespic=s=640x240:split_channels=1:colors=white"', // Creates an image of the audio waveform. - '-threads 1', - ].filter(Boolean) as string[] // remove undefined values + ...(hasVideoStream && seekTimeCode ? [`-ss`, `${seekTimeCode}`] : []), + `-i`, + input, + `-f`, + `image2`, + '-frames:v', + '1', + ...(hasVideoStream + ? [ + `-vf`, + `${!seekTimeCode ? 'thumbnail,' : ''}scale=${metadata.version.width}:${metadata.version.height}`, // Creates a thumbnail of the video. + ] + : [ + '-filter_complex', + 'showwavespic=s=640x240:split_channels=1:colors=white', // Creates an image of the audio waveform. + ]), + + '-threads', + '1', + ] } /** Returns arguments for FFMpeg to generate a proxy video file */ @@ -418,10 +439,13 @@ export function proxyFFMpegArguments( '-hide_banner', '-y', // Overwrite output files without asking. seekableSource ? undefined : '-seekable 0', - `-i "${input}"`, // Input file path + `-i`, + input, // Input file path - '-c copy', // Stream copy, no transcoding - '-threads 1', // Number of threads to use + '-c', + 'copy', // Stream copy, no transcoding + '-threads', + '1', // Number of threads to use ] // Check target to see if we should tell ffmpeg which format to use: diff --git a/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib/scan.ts b/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib/scan.ts index 6f91c64f..6b0fabe7 100644 --- a/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib/scan.ts +++ b/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib/scan.ts @@ -68,7 +68,7 @@ export function scanWithFFProbe( } const file = process.platform === 'win32' ? 'ffprobe.exe' : 'ffprobe' // Use FFProbe to scan the file: - const args = ['-hide_banner', `-i "${inputPath}"`, '-show_streams', '-show_format', '-print_format', 'json'] + const args = ['-hide_banner', `-i`, inputPath, '-show_streams', '-show_format', '-print_format', 'json'] let ffProbeProcess: ChildProcess | undefined = undefined onCancel(() => { ffProbeProcess?.stdin?.write('q') // send "q" to quit, because .kill() doesn't quite do it. @@ -164,8 +164,10 @@ export function scanFieldOrder( const file = getFFMpegExecutable() const args = [ '-hide_banner', - '-filter:v idet', - `-frames:v ${targetVersion.fieldOrderScanDuration || 200}`, + '-filter:v', + 'idet', + `-frames:v`, + `${targetVersion.fieldOrderScanDuration || 200}`, '-an', '-f', 'rawvideo', @@ -263,7 +265,7 @@ export function scanMoreInfo( if (filterString) { filterString += ',' } - filterString += `"select='gt(scene,${targetVersion.sceneThreshold || 0.4})',showinfo"` + filterString += `select='gt(scene,${targetVersion.sceneThreshold || 0.4})',showinfo` } const args = ['-hide_banner'] @@ -272,8 +274,8 @@ export function scanMoreInfo( args.push('-filter:v', filterString) args.push('-an') - args.push('-f null') - args.push('-threads 1') + args.push('-f', 'null') + args.push('-threads', '1') args.push('-') let ffMpegProcess: ChildProcess | undefined = undefined @@ -660,21 +662,21 @@ async function getFFMpegInputArgsFromAccessorHandle( ): Promise { const args: string[] = [] if (isLocalFolderAccessorHandle(sourceHandle)) { - args.push(`-i "${sourceHandle.fullPath}"`) + args.push(`-i`, sourceHandle.fullPath) } else if (isFileShareAccessorHandle(sourceHandle)) { await sourceHandle.prepareFileAccess() - args.push(`-i "${sourceHandle.fullPath}"`) + args.push(`-i`, sourceHandle.fullPath) } else if (isHTTPAccessorHandle(sourceHandle)) { - args.push(`-i "${sourceHandle.fullUrl}"`) + args.push(`-i`, sourceHandle.fullUrl) } else if (isHTTPProxyAccessorHandle(sourceHandle)) { - args.push(`-i "${sourceHandle.fullUrl}"`) + args.push(`-i`, sourceHandle.fullUrl) } else if (isQuantelClipAccessorHandle(sourceHandle)) { const httpStreamURL = await sourceHandle.getTransformerStreamURL() if (!httpStreamURL.success) throw new Error(`Source Clip not found (${httpStreamURL.reason.tech})`) - args.push('-seekable 0') - args.push(`-i "${httpStreamURL.fullURL}"`) + args.push('-seekable', '0') + args.push(`-i`, httpStreamURL.fullURL) } else { assertNever(sourceHandle) } diff --git a/shared/packages/worker/src/worker/workers/windowsWorker/windowsWorker.ts b/shared/packages/worker/src/worker/workers/windowsWorker/windowsWorker.ts index fdf4ff16..41dc92ba 100644 --- a/shared/packages/worker/src/worker/workers/windowsWorker/windowsWorker.ts +++ b/shared/packages/worker/src/worker/workers/windowsWorker/windowsWorker.ts @@ -49,9 +49,6 @@ export class WindowsWorker extends GenericWorker { sendMessageToManager: ExpectationManagerWorkerAgent.MessageFromWorker ) { super(logger.category('WindowsWorker'), agentAPI, sendMessageToManager, WindowsWorker.type) - if (process.platform !== 'win32' && process.env.JEST_WORKER_ID === undefined) { - throw new Error('The Worker is a Windows-only application') - } this.logger.debug(`Worker started`) } async doYouSupportExpectation(exp: Expectation.Any): Promise { From ee20db9d12177757d4c82c3e89a3f3cf632dc5ff Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Mon, 22 Jan 2024 12:54:25 +0100 Subject: [PATCH 2/6] chore: fix multi-platform tests --- .../src/__tests__/basic.spec.ts | 103 +++++++++--------- tests/internal-tests/src/__tests__/lib/lib.ts | 2 +- 2 files changed, 54 insertions(+), 51 deletions(-) diff --git a/tests/internal-tests/src/__tests__/basic.spec.ts b/tests/internal-tests/src/__tests__/basic.spec.ts index 04c737a7..fde37985 100644 --- a/tests/internal-tests/src/__tests__/basic.spec.ts +++ b/tests/internal-tests/src/__tests__/basic.spec.ts @@ -67,7 +67,7 @@ describeForAllPlatforms( QGatewayClient.resetMock() }) }, - () => { + (platform) => { test('Be able to copy local file', async () => { fs.__mockSetFile('/sources/source0/file0Source.mp4', 1234) fs.__mockSetDirectory('/targets/target0') @@ -116,62 +116,65 @@ describeForAllPlatforms( size: 1234, }) }) - test('Be able to copy Networked file to local', async () => { - fs.__mockSetFile('\\\\networkShare/sources/source1/file0Source.mp4', 1234) - fs.__mockSetDirectory('/targets/target1') - env.expectationManager.updateExpectations({ - [EXP_copy0]: literal({ - id: EXP_copy0, - priority: 0, - managerId: MANAGER0, - fromPackages: [{ id: PACKAGE0, expectedContentVersionHash: 'abcd1234' }], - type: Expectation.Type.FILE_COPY, - statusReport: { - label: `Copy file0`, - description: `Copy file0 because test`, - requiredForPlayout: true, - displayRank: 0, - sendReport: true, - }, - startRequirement: { - sources: [getFileShareSource(SOURCE1, 'file0Source.mp4')], - }, - endRequirement: { - targets: [getLocalTarget(TARGET1, 'subFolder0/file0Target.mp4')], - content: { - filePath: 'subFolder0/file0Target.mp4', + if (platform === 'win32') { + test('Be able to copy Networked file to local', async () => { + fs.__mockSetFile('\\\\networkShare/sources/source1/file0Source.mp4', 1234) + fs.__mockSetDirectory('/targets/target1') + + env.expectationManager.updateExpectations({ + [EXP_copy0]: literal({ + id: EXP_copy0, + priority: 0, + managerId: MANAGER0, + fromPackages: [{ id: PACKAGE0, expectedContentVersionHash: 'abcd1234' }], + type: Expectation.Type.FILE_COPY, + statusReport: { + label: `Copy file0`, + description: `Copy file0 because test`, + requiredForPlayout: true, + displayRank: 0, + sendReport: true, }, - version: { type: Expectation.Version.Type.FILE_ON_DISK }, - }, - workOptions: {}, - }), - }) + startRequirement: { + sources: [getFileShareSource(SOURCE1, 'file0Source.mp4')], + }, + endRequirement: { + targets: [getLocalTarget(TARGET1, 'subFolder0/file0Target.mp4')], + content: { + filePath: 'subFolder0/file0Target.mp4', + }, + version: { type: Expectation.Version.Type.FILE_ON_DISK }, + }, + workOptions: {}, + }), + }) - // Wait for the job to complete: - await waitUntil(() => { - expect(env.containerStatuses[TARGET1]).toBeTruthy() - expect(env.containerStatuses[TARGET1].packages[PACKAGE0]).toBeTruthy() - expect(env.containerStatuses[TARGET1].packages[PACKAGE0].packageStatus?.status).toEqual( - ExpectedPackageStatusAPI.PackageContainerPackageStatusStatus.READY - ) - }, env.WAIT_JOB_TIME) + // Wait for the job to complete: + await waitUntil(() => { + expect(env.containerStatuses[TARGET1]).toBeTruthy() + expect(env.containerStatuses[TARGET1].packages[PACKAGE0]).toBeTruthy() + expect(env.containerStatuses[TARGET1].packages[PACKAGE0].packageStatus?.status).toEqual( + ExpectedPackageStatusAPI.PackageContainerPackageStatusStatus.READY + ) + }, env.WAIT_JOB_TIME) - expect(env.expectationStatuses[EXP_copy0].statusInfo.status).toEqual('fulfilled') + expect(env.expectationStatuses[EXP_copy0].statusInfo.status).toEqual('fulfilled') - expect(await WND.list()).toEqual({ - X: { - driveLetter: 'X', - path: '\\\\networkShare\\sources\\source1\\', - status: true, - statusMessage: 'Mock', - }, - }) + expect(await WND.list()).toEqual({ + X: { + driveLetter: 'X', + path: '\\\\networkShare\\sources\\source1\\', + status: true, + statusMessage: 'Mock', + }, + }) - expect(await fsStat('/targets/target1/subFolder0/file0Target.mp4')).toMatchObject({ - size: 1234, + expect(await fsStat('/targets/target1/subFolder0/file0Target.mp4')).toMatchObject({ + size: 1234, + }) }) - }) + } test('Be able to copy Quantel clips', async () => { const orgClip = QGatewayClient.searchClip((clip) => clip.ClipGUID === 'abc123')[0] diff --git a/tests/internal-tests/src/__tests__/lib/lib.ts b/tests/internal-tests/src/__tests__/lib/lib.ts index f9493b23..fa727a24 100644 --- a/tests/internal-tests/src/__tests__/lib/lib.ts +++ b/tests/internal-tests/src/__tests__/lib/lib.ts @@ -51,7 +51,7 @@ export function describeForAllPlatforms(name: string, cbOnce: () => void, cbPerP beforeAll(async () => { orgProcessPlatform = process.platform Object.defineProperty(process, 'platform', { - value: 'darwin', + value: platform, }) }) afterAll(() => { From 30f3901c06e2f0c4fb4b5e79ada2e85f652616fb Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 22 Jan 2024 12:03:19 +0000 Subject: [PATCH 3/6] chore: disable fail-fast in ci --- .github/workflows/lint-and-test.yaml | 1 + .github/workflows/publish-prerelease.yaml | 31 +++++++++++++---------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 22882b74..c01cae9c 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -46,6 +46,7 @@ jobs: name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: node_version: ['16'] os: [ubuntu-latest, windows-latest] # [windows-latest, macOS-latest] diff --git a/.github/workflows/publish-prerelease.yaml b/.github/workflows/publish-prerelease.yaml index 41e6afb2..c8a2e3f7 100644 --- a/.github/workflows/publish-prerelease.yaml +++ b/.github/workflows/publish-prerelease.yaml @@ -6,26 +6,31 @@ on: jobs: test: - name: Test - runs-on: ubuntu-latest - timeout-minutes: 15 - + name: Test on node ${{ matrix.node_version }} and ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + node_version: ['16'] + os: [ubuntu-latest, windows-latest] # [windows-latest, macOS-latest] + timeout-minutes: 10 steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v4 - - name: Use Node.js + - name: Use Node.js ${{ matrix.node_version }} uses: actions/setup-node@v4 with: - node-version: 16 + node-version: ${{ matrix.node_version }} + - name: Prepare Environment - run: yarn - env: - CI: true - - name: Build - run: yarn build + run: | + yarn + yarn build env: CI: true - - name: Run tests - run: yarn test:ci + - name: Run unit tests + run: | + yarn test:ci env: CI: true From e150adc1a677ffc8df6b2f011c1ac3eb3bd0d475 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 22 Jan 2024 12:15:16 +0000 Subject: [PATCH 4/6] chore: increase network timeout --- .github/workflows/lint-and-test.yaml | 5 ++++- .github/workflows/publish-prerelease.yaml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index c01cae9c..71caf23f 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -29,7 +29,9 @@ jobs: key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - name: Prepare Environment - run: yarn + run: | + yarn config set network-timeout 100000 -g + yarn env: CI: true - name: Type check @@ -66,6 +68,7 @@ jobs: - name: Prepare Environment run: | + yarn config set network-timeout 100000 -g yarn yarn build env: diff --git a/.github/workflows/publish-prerelease.yaml b/.github/workflows/publish-prerelease.yaml index c8a2e3f7..c51177e3 100644 --- a/.github/workflows/publish-prerelease.yaml +++ b/.github/workflows/publish-prerelease.yaml @@ -24,6 +24,7 @@ jobs: - name: Prepare Environment run: | + yarn config set network-timeout 100000 -g yarn yarn build env: @@ -64,7 +65,9 @@ jobs: fi - name: Prepare Environment if: ${{ steps.do-publish.outputs.publish }} - run: yarn + run: | + yarn config set network-timeout 100000 -g + yarn env: CI: true - name: Build From 5e4ee3430bc27928e7f582dcaff73f7c1c5454a3 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Mon, 22 Jan 2024 13:16:10 +0100 Subject: [PATCH 5/6] chore: fix failing unit test in CI --- .../src/__mocks__/child_process.ts | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/tests/internal-tests/src/__mocks__/child_process.ts b/tests/internal-tests/src/__mocks__/child_process.ts index af5a0aef..721fe02e 100644 --- a/tests/internal-tests/src/__mocks__/child_process.ts +++ b/tests/internal-tests/src/__mocks__/child_process.ts @@ -89,7 +89,7 @@ async function robocopy(spawned: SpawnedProcess, args: string[]) { let destinationFolder const files = [] for (const arg of args) { - const options = [ + const options: (string | RegExp)[] = [ '/s', '/e', '/lev:', @@ -131,9 +131,16 @@ async function robocopy(spawned: SpawnedProcess, args: string[]) { let isOption = false for (const option of options) { - if (arg.match(new RegExp(option))) { - isOption = true - break + if (typeof option === 'string') { + if (arg === option) { + isOption = true + break + } + } else { + if (arg.match(new RegExp(option))) { + isOption = true + break + } } } if (!isOption) { @@ -143,9 +150,22 @@ async function robocopy(spawned: SpawnedProcess, args: string[]) { } } - if (!sourceFolder) throw new Error(`Mock child_process.spawn: sourceFolder not set: "${args}"`) - if (!destinationFolder) throw new Error(`Mock child_process.spawn: destinationFolder not set: "${args}"`) - if (!files.length) throw new Error(`Mock child_process.spawn: files not set: "${args}"`) + if (!sourceFolder) + throw new Error( + `Mock child_process.spawn: sourceFolder not set: "${args}" (destinationFolder: "${destinationFolder}", files: ${files.join( + ',' + )})` + ) + if (!destinationFolder) + throw new Error( + `Mock child_process.spawn: destinationFolder not set: "${args}" (sourceFolder: "${sourceFolder}", files: ${files.join( + ',' + )}) ` + ) + if (!files.length) + throw new Error( + `Mock child_process.spawn: files not set: "${args}" (sourceFolder: "${sourceFolder}", destinationFolder: "${destinationFolder}")` + ) // Just do a simple copy, expand this if needed later... try { From 87a5f35a1aadbcb224e7ff3d3137a1155e450bc9 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Fri, 26 Jan 2024 14:11:09 +0000 Subject: [PATCH 6/6] fix: atem and ffmpeg linux --- .../src/worker/accessorHandlers/atem.ts | 81 +++++++++++++------ .../expectationHandlers/lib/ffmpeg.ts | 4 +- 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/shared/packages/worker/src/worker/accessorHandlers/atem.ts b/shared/packages/worker/src/worker/accessorHandlers/atem.ts index d0d32f92..4bbd5f7e 100644 --- a/shared/packages/worker/src/worker/accessorHandlers/atem.ts +++ b/shared/packages/worker/src/worker/accessorHandlers/atem.ts @@ -155,12 +155,11 @@ export class ATEMAccessorHandle extends GenericAccessorHandle { - atem.off('stateChanged', successHandler) + atem.off('connected', successHandler) atem.off('error', errorHandler) } - atem.once('stateChanged', successHandler) - + atem.once('connected', successHandler) atem.once('error', errorHandler) }) } @@ -226,9 +225,11 @@ export class ATEMAccessorHandle extends GenericAccessorHandle { // can't really abort the write stream + aborted = true }) streamWrapper.usingCustomProgressEvent = true @@ -251,10 +252,12 @@ export class ATEMAccessorHandle extends GenericAccessorHandle extends GenericAccessorHandle extends GenericAccessorHandle maxDuration) { throw new Error(`File is too long in duration (${duration} frames, max ${maxDuration})`) @@ -292,6 +298,7 @@ export class ATEMAccessorHandle extends GenericAccessorHandle extends GenericAccessorHandle extends GenericAccessorHandle { for (let i = 0; i < allRGBAs.length; i++) { + if (aborted) throw new Error('Aborted') + + streamWrapper.emit('progress', 0.61 + 0.29 * ((i - 0.5) / allRGBAs.length)) yield await fsReadFile(allRGBAs[i]) - streamWrapper.emit('progress', 0.6 + 0.3 * (i / allRGBAs.length)) + streamWrapper.emit('progress', 0.61 + 0.29 * (i / allRGBAs.length)) } } - await atem.uploadClip(bankIndex, provideFrame(), this.getAtemClipName()) + try { + await atem.uploadClip(bankIndex, provideFrame(), this.getAtemClipName()) + } catch (e) { + if (`${e}`.match(/Aborted/)) { + return + } else throw e + } + if (aborted) return const audioStreamIndicies = await getStreamIndicies(inputFile, 'audio') if (audioStreamIndicies.length > 0) { await convertAudio(inputFile) await sleep(1000) // Helps avoid a lock-related "Code 5" error from the ATEM. + + if (aborted) return const audioBuffer = await fsReadFile(replaceFileExtension(inputFile, '.wav')) await atem.uploadAudio(bankIndex, audioBuffer, `audio${this.accessor.bankIndex}`) } @@ -484,30 +504,31 @@ async function stream2Disk(sourceStream: NodeJS.ReadableStream, outputFile: stri async function createTGASequence(inputFile: string, opts?: { width: number; height: number }): Promise { const outputFile = replaceFileExtension(inputFile, '_%04d.tga') - const args = [`-i "${inputFile}"`] + const args = ['-i', inputFile] if (opts) { - args.push(`-vf scale=${opts.width}:${opts.height}`) + args.push('-vf', `scale=${opts.width}:${opts.height}`) } - args.push(`"${outputFile}"`) + args.push(outputFile) return ffmpeg(args) } async function convertFrameToRGBA(inputFile: string): Promise { const outputFile = replaceFileExtension(inputFile, '.rgba') - const args = [`-i "${inputFile}"`, '-pix_fmt rgba', '-f rawvideo', `"${outputFile}"`] + const args = [`-i`, inputFile, '-pix_fmt', 'rgba', '-f', 'rawvideo', outputFile] return ffmpeg(args) } async function convertAudio(inputFile: string): Promise { const outputFile = replaceFileExtension(inputFile, '.wav') const args = [ - `-i "${inputFile}"`, + `-i`, + inputFile, '-vn', // no video - '-ar 48000', // 48kHz sample rate - '-ac 2', // stereo audio - '-c:a pcm_s24le', - `"${outputFile}"`, + '-ar`,`48000', // 48kHz sample rate + '-ac`,`2', // stereo audio + '-c:a`,`pcm_s24le', + outputFile, ] return ffmpeg(args) @@ -516,12 +537,17 @@ async function convertAudio(inputFile: string): Promise { async function countFrames(inputFile: string): Promise { return new Promise((resolve, reject) => { const args = [ - `-i "${inputFile}"`, - '-v error', - '-select_streams v:0', + '-i', + inputFile, + '-v', + 'error', + '-select_streams', + 'v:0', '-count_frames', - '-show_entries stream=nb_read_frames', - '-print_format csv', + '-show_entries', + 'stream=nb_read_frames', + '-print_format', + 'csv', ] ffprobe(args) @@ -537,11 +563,16 @@ async function countFrames(inputFile: string): Promise { async function getStreamIndicies(inputFile: string, type: 'video' | 'audio'): Promise { return new Promise((resolve, reject) => { const args = [ - `-i "${inputFile}"`, - '-v error', - `-select_streams ${type === 'video' ? 'v' : 'a'}`, - '-show_entries stream=index', - '-of csv=p=0', + '-i', + inputFile, + '-v', + 'error', + '-select_streams', + type === 'video' ? 'v' : 'a', + '-show_entries', + 'stream=index', + '-of', + 'csv=p=0', ] ffprobe(args) @@ -582,7 +613,7 @@ async function ffmpeg(args: string[]): Promise { const file = getFFMpegExecutable() execFile( file, - ['-v error', ...args], + ['-v', 'error', ...args], { maxBuffer: MAX_EXEC_BUFFER, windowsVerbatimArguments: true, // To fix an issue with ffmpeg.exe on Windows diff --git a/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib/ffmpeg.ts b/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib/ffmpeg.ts index 3810da00..c1bcbf99 100644 --- a/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib/ffmpeg.ts +++ b/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib/ffmpeg.ts @@ -88,11 +88,11 @@ export async function spawnFFMpeg( let pipeStdOut = false if (isLocalFolderAccessorHandle(targetHandle)) { await mkdirp(path.dirname(targetHandle.fullPath)) // Create folder if it doesn't exist - args.push(`"${targetHandle.fullPath}"`) + args.push(targetHandle.fullPath) } else if (isFileShareAccessorHandle(targetHandle)) { await targetHandle.prepareFileAccess() await mkdirp(path.dirname(targetHandle.fullPath)) // Create folder if it doesn't exist - args.push(`"${targetHandle.fullPath}"`) + args.push(targetHandle.fullPath) } else if (isHTTPProxyAccessorHandle(targetHandle)) { pipeStdOut = true args.push('pipe:1') // pipe output to stdout