diff --git a/shared/packages/api/src/__tests__/filePath.spec.ts b/shared/packages/api/src/__tests__/filePath.spec.ts new file mode 100644 index 00000000..e1bdec08 --- /dev/null +++ b/shared/packages/api/src/__tests__/filePath.spec.ts @@ -0,0 +1,8 @@ +import { escapeFilePath } from '../filePath' + +describe('filePath', () => { + test('checkPath', () => { + expect(escapeFilePath('test/path')).toBe(process.platform === 'win32' ? '"test/path"' : 'test/path') + expect(escapeFilePath('C:\\test\\path')).toBe(process.platform === 'win32' ? '"C:\\test\\path"' : 'C:\\test\\path') + }) +}) diff --git a/shared/packages/api/src/filePath.ts b/shared/packages/api/src/filePath.ts new file mode 100644 index 00000000..6557f2d0 --- /dev/null +++ b/shared/packages/api/src/filePath.ts @@ -0,0 +1,10 @@ +/** + * Escape spaces in file path with double quotes on windows. + * + * @param {string} path File path to be escaped. + * @returns {string} Escaped file path. + * @see {@link https://ffmpeg.org/ffmpeg-utils.html#Quoting-and-escaping} + */ +export function escapeFilePath(path: string): string { + return process.platform === 'win32' ? `"${path}"` : path +} diff --git a/shared/packages/api/src/index.ts b/shared/packages/api/src/index.ts index d0d593b3..b0ebe221 100644 --- a/shared/packages/api/src/index.ts +++ b/shared/packages/api/src/index.ts @@ -2,6 +2,7 @@ export * from './adapterClient' export * from './adapterServer' export * from './appContainer' export * from './config' +export * from './filePath' export * from './expectationApi' export * from './inputApi' export * from './HelpfulEventEmitter' 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 36dd628c..50c80b15 100644 --- a/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib.ts +++ b/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/lib.ts @@ -19,6 +19,7 @@ import { AccessorId, promiseTimeout, INNER_ACTION_TIMEOUT, + escapeFilePath, } from '@sofie-package-manager/api' import { LocalFolderAccessorHandle } from '../../../accessorHandlers/localFolder' import { FileShareAccessorHandle } from '../../../accessorHandlers/fileShare' @@ -451,9 +452,9 @@ export function proxyFFMpegArguments( // Check target to see if we should tell ffmpeg which format to use: let targetPath = '' if (isLocalFolderAccessorHandle(targetHandle)) { - targetPath = targetHandle.fullPath + targetPath = escapeFilePath(targetHandle.fullPath) } else if (isFileShareAccessorHandle(targetHandle)) { - targetPath = targetHandle.fullPath + targetPath = escapeFilePath(targetHandle.fullPath) } else if (isHTTPProxyAccessorHandle(targetHandle)) { targetPath = '' } else { 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 51eacbe2..81358dcf 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 @@ -9,7 +9,7 @@ import { import { FileShareAccessorHandle } from '../../../../accessorHandlers/fileShare' import { HTTPProxyAccessorHandle } from '../../../../accessorHandlers/httpProxy' import { LocalFolderAccessorHandle } from '../../../../accessorHandlers/localFolder' -import { assertNever, stringifyError } from '@sofie-package-manager/api' +import { assertNever, escapeFilePath, stringifyError } from '@sofie-package-manager/api' export interface OverriddenFFMpegExecutables { ffmpeg: string @@ -104,11 +104,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(escapeFilePath(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(escapeFilePath(targetHandle.fullPath)) } else if (isHTTPProxyAccessorHandle(targetHandle)) { pipeStdOut = true args.push('pipe:1') // pipe output to stdout 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 bccbf0e4..aee3a37d 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 @@ -1,5 +1,12 @@ import { execFile, ChildProcess, spawn } from 'child_process' -import { Expectation, assertNever, Accessor, AccessorOnPackage, LoggerInstance } from '@sofie-package-manager/api' +import { + Expectation, + assertNever, + Accessor, + AccessorOnPackage, + LoggerInstance, + escapeFilePath, +} from '@sofie-package-manager/api' import { isQuantelClipAccessorHandle, isLocalFolderAccessorHandle, @@ -50,12 +57,12 @@ export function scanWithFFProbe( let inputPath: string let filePath: string if (isLocalFolderAccessorHandle(sourceHandle)) { - inputPath = sourceHandle.fullPath - filePath = sourceHandle.filePath + inputPath = escapeFilePath(sourceHandle.fullPath) + filePath = escapeFilePath(sourceHandle.filePath) } else if (isFileShareAccessorHandle(sourceHandle)) { await sourceHandle.prepareFileAccess() - inputPath = sourceHandle.fullPath - filePath = sourceHandle.filePath + inputPath = escapeFilePath(sourceHandle.fullPath) + filePath = escapeFilePath(sourceHandle.filePath) } else if (isHTTPAccessorHandle(sourceHandle)) { inputPath = sourceHandle.fullUrl filePath = sourceHandle.path @@ -650,10 +657,10 @@ async function getFFMpegInputArgsFromAccessorHandle( ): Promise { const args: string[] = [] if (isLocalFolderAccessorHandle(sourceHandle)) { - args.push(`-i`, sourceHandle.fullPath) + args.push(`-i`, escapeFilePath(sourceHandle.fullPath)) } else if (isFileShareAccessorHandle(sourceHandle)) { await sourceHandle.prepareFileAccess() - args.push(`-i`, sourceHandle.fullPath) + args.push(`-i`, escapeFilePath(sourceHandle.fullPath)) } else if (isHTTPAccessorHandle(sourceHandle)) { args.push(`-i`, sourceHandle.fullUrl) } else if (isHTTPProxyAccessorHandle(sourceHandle)) { diff --git a/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/mediaFilePreview.ts b/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/mediaFilePreview.ts index 17ebdfe8..adc653c6 100644 --- a/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/mediaFilePreview.ts +++ b/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/mediaFilePreview.ts @@ -13,6 +13,7 @@ import { assertNever, stringifyError, startTimer, + escapeFilePath, } from '@sofie-package-manager/api' import { isFileShareAccessorHandle, @@ -220,10 +221,10 @@ export const MediaFilePreview: ExpectationWindowsHandler = { let inputPath: string if (isLocalFolderAccessorHandle(sourceHandle)) { - inputPath = sourceHandle.fullPath + inputPath = escapeFilePath(sourceHandle.fullPath) } else if (isFileShareAccessorHandle(sourceHandle)) { await sourceHandle.prepareFileAccess() - inputPath = sourceHandle.fullPath + inputPath = escapeFilePath(sourceHandle.fullPath) } else if (isHTTPAccessorHandle(sourceHandle)) { inputPath = sourceHandle.fullUrl } else if (isHTTPProxyAccessorHandle(sourceHandle)) { diff --git a/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/mediaFileThumbnail.ts b/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/mediaFileThumbnail.ts index 27796663..e0db385e 100644 --- a/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/mediaFileThumbnail.ts +++ b/shared/packages/worker/src/worker/workers/windowsWorker/expectationHandlers/mediaFileThumbnail.ts @@ -10,6 +10,7 @@ import { assertNever, stringifyError, startTimer, + escapeFilePath, } from '@sofie-package-manager/api' import { getStandardCost } from '../lib/lib' import { GenericWorker } from '../../../worker' @@ -220,10 +221,10 @@ export const MediaFileThumbnail: ExpectationWindowsHandler = { let inputPath: string if (isLocalFolderAccessorHandle(sourceHandle)) { - inputPath = sourceHandle.fullPath + inputPath = escapeFilePath(sourceHandle.fullPath) } else if (isFileShareAccessorHandle(sourceHandle)) { await sourceHandle.prepareFileAccess() - inputPath = sourceHandle.fullPath + inputPath = escapeFilePath(sourceHandle.fullPath) } else if (isHTTPAccessorHandle(sourceHandle)) { inputPath = sourceHandle.fullUrl } else if (isHTTPProxyAccessorHandle(sourceHandle)) {