From e16d235a3894a2a20c45ebdc146eec9fccaaabe6 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Tue, 29 Jan 2019 00:05:00 +0100 Subject: [PATCH] allow merge with multi cut should cover #98 --- src/ffmpeg.js | 43 +++++++++++++++++++++++++++++++------------ src/renderer.jsx | 32 +++++++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/ffmpeg.js b/src/ffmpeg.js index 80466812..367d401e 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -54,14 +54,9 @@ function handleProgress(process, cutDuration, onProgress) { } async function cut({ - customOutDir, filePath, format, cutFrom, cutTo, cutToApparent, videoDuration, rotation, - includeAllStreams, onProgress, stripAudio, keyframeCut, + filePath, format, cutFrom, cutTo, cutToApparent, videoDuration, rotation, + includeAllStreams, onProgress, stripAudio, keyframeCut, outPath, }) { - const ext = path.extname(filePath) || `.${format}`; - const cutSpecification = `${formatDuration(cutFrom, true)}-${formatDuration(cutToApparent, true)}`; - - const outPath = getOutPath(customOutDir, filePath, `${cutSpecification}${ext}`); - console.log('Cutting from', cutFrom, 'to', cutToApparent); const cutDuration = cutToApparent - cutFrom; @@ -121,11 +116,19 @@ async function cutMultiple({ return onProgress((sum(Object.values(singleProgresses)) / segments.length)); } + const outFiles = []; + let i = 0; // eslint-disable-next-line no-restricted-syntax for (const { cutFrom, cutTo, cutToApparent } of segments) { + const ext = path.extname(filePath) || `.${format}`; + const cutSpecification = `${formatDuration(cutFrom, true)}-${formatDuration(cutToApparent, true)}`; + + const outPath = getOutPath(customOutDir, filePath, `${cutSpecification}${ext}`); + // eslint-disable-next-line no-await-in-loop await cut({ + outPath, customOutDir, filePath, format, @@ -140,8 +143,13 @@ async function cutMultiple({ // eslint-disable-next-line no-loop-func onProgress: progress => onSingleProgress(i, progress), }); + + outFiles.push(outPath); + i += 1; } + + return outFiles; } async function html5ify(filePath, outPath, encodeVideo) { @@ -166,10 +174,7 @@ async function html5ify(filePath, outPath, encodeVideo) { await transferTimestamps(filePath, outPath); } -async function mergeFiles(paths) { - const firstPath = paths[0]; - const ext = path.extname(firstPath); - const outPath = `${firstPath}-merged${ext}`; +async function mergeFiles(paths, outPath) { console.log('Merging files', { paths }, 'to', outPath); // https://blog.yo1.dog/fix-for-ffmpeg-protocol-not-on-whitelist-error-for-urls/ @@ -196,6 +201,19 @@ async function mergeFiles(paths) { console.log(result.stdout); } +async function mergeAnyFiles(paths) { + const firstPath = paths[0]; + const ext = path.extname(firstPath); + const outPath = `${firstPath}-merged${ext}`; + return mergeFiles(paths, outPath); +} + +async function autoMergeSegments({ customOutDir, sourceFile, segmentPaths }) { + const ext = path.extname(sourceFile); + const outPath = getOutPath(customOutDir, sourceFile, `cut-merged-${new Date().getTime()}${ext}`); + return mergeFiles(segmentPaths, outPath); +} + /** * ffmpeg only supports encoding certain formats, and some of the detected input * formats are not the same as the names used for encoding. @@ -311,6 +329,7 @@ module.exports = { cutMultiple, getFormat, html5ify, - mergeFiles, + mergeAnyFiles, + autoMergeSegments, extractAllStreams, }; diff --git a/src/renderer.jsx b/src/renderer.jsx index 9da1a723..e6da209f 100644 --- a/src/renderer.jsx +++ b/src/renderer.jsx @@ -95,6 +95,7 @@ const globalState = { captureFormat: 'jpeg', customOutDir: undefined, keyframeCut: true, + autoMerge: false, }; class App extends React.Component { @@ -169,7 +170,7 @@ class App extends React.Component { // TODO customOutDir ? // console.log('merge', paths); - await ffmpeg.mergeFiles(paths); + await ffmpeg.mergeAnyFiles(paths); } catch (err) { errorToast('Failed to merge files. Make sure they are all of the exact same format and codecs'); console.error('Failed to merge files', err); @@ -343,6 +344,8 @@ class App extends React.Component { toggleKeyframeCut = () => this.setState(({ keyframeCut }) => ({ keyframeCut: !keyframeCut })); + toggleAutoMerge = () => this.setState(({ autoMerge }) => ({ autoMerge: !autoMerge })); + addCutSegment = () => { const { cutSegments, currentTime, duration } = this.state; @@ -424,7 +427,7 @@ class App extends React.Component { cutClick = async () => { const { filePath, customOutDir, fileFormat, duration, includeAllStreams, - stripAudio, keyframeCut, working, cutSegments, + stripAudio, keyframeCut, autoMerge, working, cutSegments, } = this.state; if (working) { @@ -451,7 +454,7 @@ class App extends React.Component { cutToApparent: this.getApparentCutEndTime(i), })); - await ffmpeg.cutMultiple({ + const outFiles = await ffmpeg.cutMultiple({ customOutDir, filePath, format: fileFormat, @@ -463,6 +466,16 @@ class App extends React.Component { segments, onProgress: this.onCutProgress, }); + + if (outFiles.length > 1 && autoMerge) { + this.onCutProgress(0); // TODO + + await ffmpeg.autoMergeSegments({ + customOutDir, + sourceFile: filePath, + segmentPaths: outFiles, + }); + } } catch (err) { console.error('stdout:', err.stdout); console.error('stderr:', err.stderr); @@ -471,6 +484,7 @@ class App extends React.Component { errorToast('Whoops! ffmpeg was unable to cut this video. It may be of an unknown format or codec combination'); return; } + showFfmpegFail(err); } finally { this.setState({ working: false }); @@ -565,7 +579,7 @@ class App extends React.Component { const { working, filePath, duration: durationRaw, cutProgress, currentTime, playing, fileFormat, playbackRate, keyframeCut, includeAllStreams, stripAudio, captureFormat, - helpVisible, currentSeg, cutSegments, + helpVisible, currentSeg, cutSegments, autoMerge, } = this.state; const duration = durationRaw || 1; @@ -764,11 +778,19 @@ class App extends React.Component { + +