diff --git a/src/main/ffmpeg.ts b/src/main/ffmpeg.ts index a6656693..d3a195ff 100644 --- a/src/main/ffmpeg.ts +++ b/src/main/ffmpeg.ts @@ -11,6 +11,7 @@ import { platform, arch, isWindows, isMac, isLinux } from './util.js'; import { CaptureFormat, Html5ifyMode, Waveform } from '../../types.js'; import isDev from './isDev.js'; import logger from './logger.js'; +import { parseFfmpegProgressLine } from './progress.js'; const runningFfmpegs = new Set>(); @@ -55,9 +56,9 @@ export function abortFfmpegs() { function handleProgress( process: { stderr: Readable | null }, - durationIn: number | undefined, + duration: number | undefined, onProgress: (a: number) => void, - customMatcher: (a: string) => void = () => undefined, + customMatcher?: (a: string) => void, ) { if (!onProgress) return; if (process.stderr == null) return; @@ -68,44 +69,10 @@ function handleProgress( // console.log('progress', line); try { - // eslint-disable-next-line unicorn/better-regex - let match = line.match(/frame=\s*[^\s]+\s+fps=\s*[^\s]+\s+q=\s*[^\s]+\s+(?:size|Lsize)=\s*[^\s]+\s+time=\s*([^\s]+)\s+/); - // Audio only looks like this: "line size= 233422kB time=01:45:50.68 bitrate= 301.1kbits/s speed= 353x " - // eslint-disable-next-line unicorn/better-regex - if (!match) match = line.match(/(?:size|Lsize)=\s*[^\s]+\s+time=\s*([^\s]+)\s+/); - if (!match) { - customMatcher(line); - return; + const progress = parseFfmpegProgressLine({ line, customMatcher, duration }); + if (progress != null) { + onProgress(progress); } - - const timeStr = match[1]; - // console.log(timeStr); - const match2 = timeStr!.match(/^(-?)(\d+):(\d+):(\d+)\.(\d+)$/); - if (!match2) throw new Error(`Invalid time from ffmpeg progress ${timeStr}`); - - const sign = match2[1]; - - if (sign === '-') { - // For some reason, ffmpeg sometimes gives a negative progress, e.g. "-00:00:06.46" - // let's just ignore that - return; - } - - const h = parseInt(match2[2]!, 10); - const m = parseInt(match2[3]!, 10); - const s = parseInt(match2[4]!, 10); - const cs = parseInt(match2[5]!, 10); - const time = (((h * 60) + m) * 60 + s) + cs / 100; - // console.log(time); - - const progressTime = Math.max(0, time); - // console.log(progressTime); - - if (durationIn == null) return; - const duration = Math.max(0, durationIn); - if (duration === 0) return; - const progress = Math.min(progressTime / duration, 1); // sometimes progressTime will be greater than cutDuration - onProgress(progress); } catch (err) { logger.error('Failed to parse ffmpeg progress line:', err instanceof Error ? err.message : err); } diff --git a/src/main/pathToFileURL.test.ts b/src/main/pathToFileURL.test.ts index 212b1e39..8826d0e7 100644 --- a/src/main/pathToFileURL.test.ts +++ b/src/main/pathToFileURL.test.ts @@ -1,4 +1,3 @@ -// eslint-disable-line unicorn/filename-case // eslint-disable-next-line import/no-extraneous-dependencies import { test, expect, describe } from 'vitest'; diff --git a/src/main/progress.test.ts b/src/main/progress.test.ts new file mode 100644 index 00000000..04c1c7d3 --- /dev/null +++ b/src/main/progress.test.ts @@ -0,0 +1,18 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { describe, expect, test } from 'vitest'; +import { parseFfmpegProgressLine } from './progress'; + +describe('parseFfmpegProgressLine', () => { + test('parse video', () => { + const str = 'frame= 2285 fps=135 q=4.0 Lsize=N/A time=00:01:31.36 bitrate=N/A speed=5.38x '; + expect(parseFfmpegProgressLine({ line: str, duration: 60 + 31.36 })).toBe(1); + }); + test('parse audio 0', () => { + const str = 'size= 0kB time=00:00:00.00 bitrate=N/A speed=N/A '; + expect(parseFfmpegProgressLine({ line: str, duration: 1 })).toBe(0); + }); + test('parse audio 32.02', () => { + const str = 'size= 501kB time=00:00:32.02 bitrate= 128.2kbits/s speed=2.29e+03x '; + expect(parseFfmpegProgressLine({ line: str, duration: 32.02 })).toBe(1); + }); +}); diff --git a/src/main/progress.ts b/src/main/progress.ts new file mode 100644 index 00000000..4b2a968b --- /dev/null +++ b/src/main/progress.ts @@ -0,0 +1,46 @@ +// eslint-disable-next-line import/prefer-default-export +export function parseFfmpegProgressLine({ line, customMatcher, duration: durationIn }: { + line: string, + customMatcher?: ((a: string) => void) | undefined, + duration: number | undefined, +}) { + let match = line.match(/frame=\s*\S+\s+fps=\s*\S+\s+q=\s*\S+\s+(?:size|Lsize)=\s*\S+\s+time=\s*(\S+)\s+/); + if (!match) { + // Audio only looks like this: "size= 233422kB time=01:45:50.68 bitrate= 301.1kbits/s speed= 353x " + match = line.match(/(?:size|Lsize)=\s*\S+\s+time=\s*(\S+)\s+/); + } + if (!match) { + customMatcher?.(line); + return undefined; + } + + if (durationIn == null) return undefined; + const duration = Math.max(0, durationIn); + if (duration === 0) return undefined; + + const timeStr = match[1]; + // console.log(timeStr); + const match2 = timeStr!.match(/^(-?)(\d+):(\d+):(\d+)\.(\d+)$/); + if (!match2) throw new Error(`Invalid time from ffmpeg progress ${timeStr}`); + + const sign = match2[1]; + + if (sign === '-') { + // For some reason, ffmpeg sometimes gives a negative progress, e.g. "-00:00:06.46" + // let's just ignore those lines + return undefined; + } + + const h = parseInt(match2[2]!, 10); + const m = parseInt(match2[3]!, 10); + const s = parseInt(match2[4]!, 10); + const cs = parseInt(match2[5]!, 10); + const time = (((h * 60) + m) * 60 + s) + cs / 100; + // console.log(time); + + const progressTime = Math.max(0, time); + // console.log(progressTime); + + const progress = Math.min(progressTime / duration, 1); // sometimes progressTime will be greater than cutDuration + return progress; +}