From 91ceef397251a70584313fba191163d8f563e5eb Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 6 Sep 2023 19:56:23 +0200 Subject: [PATCH] fix broken black/silence detection #1705 --- public/ffmpeg.js | 36 +++++++++++++++++++++++++++++++++++- src/ffmpeg.js | 36 +----------------------------------- src/hooks/useSegments.js | 6 +++++- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/public/ffmpeg.js b/public/ffmpeg.js index 55a5fa71..fdad4293 100644 --- a/public/ffmpeg.js +++ b/public/ffmpeg.js @@ -277,6 +277,39 @@ async function detectIntervals({ filePath, customArgs, onProgress, from, to, mat return adjustSegmentsWithOffset({ segments, from }); } +const mapFilterOptions = (options) => Object.entries(options).map(([key, value]) => `${key}=${value}`).join(':'); + +async function blackDetect({ filePath, filterOptions, onProgress, from, to }) { + function matchLineTokens(line) { + const match = line.match(/^[blackdetect\s*@\s*0x[0-9a-f]+] black_start:([\d\\.]+) black_end:([\d\\.]+) black_duration:[\d\\.]+/); + if (!match) return {}; + return { + start: parseFloat(match[1]), + end: parseFloat(match[2]), + }; + } + const customArgs = ['-vf', `blackdetect=${mapFilterOptions(filterOptions)}`, '-an']; + return detectIntervals({ filePath, onProgress, from, to, matchLineTokens, customArgs }); +} + +async function silenceDetect({ filePath, filterOptions, onProgress, from, to }) { + function matchLineTokens(line) { + const match = line.match(/^[silencedetect\s*@\s*0x[0-9a-f]+] silence_end: ([\d\\.]+)[|\s]+silence_duration: ([\d\\.]+)/); + if (!match) return {}; + const end = parseFloat(match[1]); + const silenceDuration = parseFloat(match[2]); + if (Number.isNaN(end) || Number.isNaN(silenceDuration)) return {}; + const start = end - silenceDuration; + if (start < 0 || end <= 0 || start >= end) return {}; + return { + start, + end, + }; + } + const customArgs = ['-af', `silencedetect=${mapFilterOptions(filterOptions)}`, '-vn']; + return detectIntervals({ filePath, onProgress, from, to, matchLineTokens, customArgs }); +} + function getFffmpegJpegQuality(quality) { // Normal range for JPEG is 2-31 with 31 being the worst quality. const qMin = 2; @@ -522,7 +555,6 @@ module.exports = { renderWaveformPng, mapTimesToSegments, detectSceneChanges, - detectIntervals, captureFrames, captureFrame, getFfCommandLine, @@ -530,4 +562,6 @@ module.exports = { getDuration, getOneRawFrame, encodeLiveRawStream, + blackDetect, + silenceDetect, }; diff --git a/src/ffmpeg.js b/src/ffmpeg.js index 2045096c..5c3b6a7f 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -13,7 +13,7 @@ const { pathExists } = window.require('fs-extra'); const remote = window.require('@electron/remote'); -const { renderWaveformPng, mapTimesToSegments, detectSceneChanges, detectIntervals, captureFrames, captureFrame, getFfCommandLine, runFfmpegConcat, runFfmpegWithProgress, html5ify, getDuration, abortFfmpegs, runFfmpeg, runFfprobe, getFfmpegPath, setCustomFfPath } = remote.require('./ffmpeg'); +const { renderWaveformPng, mapTimesToSegments, detectSceneChanges, captureFrames, captureFrame, getFfCommandLine, runFfmpegConcat, runFfmpegWithProgress, html5ify, getDuration, abortFfmpegs, runFfmpeg, runFfprobe, getFfmpegPath, setCustomFfPath } = remote.require('./ffmpeg'); export { renderWaveformPng, mapTimesToSegments, detectSceneChanges, captureFrames, captureFrame, getFfCommandLine, runFfmpegConcat, runFfmpegWithProgress, html5ify, getDuration, abortFfmpegs, runFfprobe, getFfmpegPath, setCustomFfPath }; @@ -483,40 +483,6 @@ export async function renderThumbnails({ filePath, from, duration, onThumbnail } }, { concurrency: 2 }); } - -const mapFilterOptions = (options) => Object.entries(options).map(([key, value]) => `${key}=${value}`).join(':'); - -export async function blackDetect({ filePath, filterOptions, onProgress, from, to }) { - function matchLineTokens(line) { - const match = line.match(/^[blackdetect\s*@\s*0x[0-9a-f]+] black_start:([\d\\.]+) black_end:([\d\\.]+) black_duration:[\d\\.]+/); - if (!match) return {}; - return { - start: parseFloat(match[1]), - end: parseFloat(match[2]), - }; - } - const customArgs = ['-vf', `blackdetect=${mapFilterOptions(filterOptions)}`, '-an']; - return detectIntervals({ filePath, onProgress, from, to, matchLineTokens, customArgs }); -} - -export async function silenceDetect({ filePath, filterOptions, onProgress, from, to }) { - function matchLineTokens(line) { - const match = line.match(/^[silencedetect\s*@\s*0x[0-9a-f]+] silence_end: ([\d\\.]+)[|\s]+silence_duration: ([\d\\.]+)/); - if (!match) return {}; - const end = parseFloat(match[1]); - const silenceDuration = parseFloat(match[2]); - if (Number.isNaN(end) || Number.isNaN(silenceDuration)) return {}; - const start = end - silenceDuration; - if (start < 0 || end <= 0 || start >= end) return {}; - return { - start, - end, - }; - } - const customArgs = ['-af', `silencedetect=${mapFilterOptions(filterOptions)}`, '-vn']; - return detectIntervals({ filePath, onProgress, from, to, matchLineTokens, customArgs }); -} - export async function extractWaveform({ filePath, outPath }) { const numSegs = 10; const duration = 60 * 60; diff --git a/src/hooks/useSegments.js b/src/hooks/useSegments.js index ad95726b..14efe590 100644 --- a/src/hooks/useSegments.js +++ b/src/hooks/useSegments.js @@ -6,7 +6,7 @@ import pMap from 'p-map'; import sortBy from 'lodash/sortBy'; -import { blackDetect, silenceDetect, detectSceneChanges as ffmpegDetectSceneChanges, readFrames, mapTimesToSegments, findKeyframeNearTime } from '../ffmpeg'; +import { detectSceneChanges as ffmpegDetectSceneChanges, readFrames, mapTimesToSegments, findKeyframeNearTime } from '../ffmpeg'; import { handleError, shuffleArray } from '../util'; import { errorToast } from '../swal'; import { showParametersDialog } from '../dialogs/parameters'; @@ -15,6 +15,10 @@ import { createSegment, findSegmentsAtCursor, sortSegments, invertSegments, getS import * as ffmpegParameters from '../ffmpeg-parameters'; import { maxSegmentsAllowed } from '../util/constants'; +const remote = window.require('@electron/remote'); + +const { blackDetect, silenceDetect } = remote.require('./ffmpeg'); + export default ({ filePath, workingRef, setWorking, setCutProgress, mainVideoStream,