diff --git a/src/App.jsx b/src/App.jsx index 611121e7..bb42146e 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -44,7 +44,7 @@ import SubtitleControl from './components/SubtitleControl'; import BatchFilesList from './components/BatchFilesList'; import ConcatDialog from './components/ConcatDialog'; import KeyboardShortcuts from './components/KeyboardShortcuts'; -import Loading from './components/Loading'; +import Working from './components/Working'; import OutputFormatSelect from './components/OutputFormatSelect'; import { loadMifiLink, runStartupCheck } from './mifi'; @@ -55,7 +55,7 @@ import { extractStreams, setCustomFfPath as ffmpegSetCustomFfPath, isIphoneHevc, isProblematicAvc1, tryMapChaptersToEdl, blackDetect, silenceDetect, detectSceneChanges as ffmpegDetectSceneChanges, getDuration, getTimecodeFromStreams, createChaptersFromSegments, extractSubtitleTrack, - RefuseOverwriteError, readFrames, mapTimesToSegments, + RefuseOverwriteError, readFrames, mapTimesToSegments, abortFfmpegs, } from './ffmpeg'; import { shouldCopyStreamByDefault, getAudioStreams, getRealVideoStreams, isAudioDefinitelyNotSupported, willPlayerProperlyHandleVideo, doesPlayerSupportHevcPlayback } from './util/streams'; import { exportEdlFile, readEdlFile, saveLlcProject, loadLlcProject, askForEdlImport } from './edlStore'; @@ -1371,6 +1371,10 @@ const App = memo(() => { const revealPath = concatOutPath || outFiles[0]; if (!hideAllNotifications) openCutFinishedToast({ filePath: revealPath, warnings, notices }); } catch (err) { + if (err.killed === true) { + // assume execa killed (aborted by user) + return; + } if (err instanceof RefuseOverwriteError) { showRefuseToOverwrite(); return; @@ -2484,7 +2488,7 @@ const App = memo(() => { )} - {working && } + {working && } {tunerVisible && setTunerVisible()} />} diff --git a/src/components/Loading.jsx b/src/components/Working.jsx similarity index 54% rename from src/components/Loading.jsx rename to src/components/Working.jsx index 8e10d0d2..dc354140 100644 --- a/src/components/Loading.jsx +++ b/src/components/Working.jsx @@ -1,39 +1,45 @@ import React, { memo } from 'react'; import { motion } from 'framer-motion'; import Lottie from 'react-lottie-player'; +import { Button } from 'evergreen-ui'; +import { Trans } from 'react-i18next'; import { primaryColor } from '../colors'; import loadingLottie from '../7077-magic-flow.json'; -const Loading = memo(({ text, cutProgress }) => ( +const Working = memo(({ text, cutProgress, onAbortClick }) => (
-
+
-
+
{text}...
{(cutProgress != null) && ( -
+
{`${(cutProgress * 100).toFixed(1)} %`}
)} + +
+ +
)); -export default Loading; +export default Working; diff --git a/src/dialogs/extractFrames.jsx b/src/dialogs/extractFrames.jsx index 91d963cf..68aaa79a 100644 --- a/src/dialogs/extractFrames.jsx +++ b/src/dialogs/extractFrames.jsx @@ -30,7 +30,7 @@ export async function askExtractFramesAsImages({ segmentNumFrames, fps }) { text: i18n.t('Capture the best image every nth second'), icon: 'question', input: 'text', - inputLabel: i18n.t('Enter a decimal number of seconds'), + inputLabel: i18n.t('Enter the number of seconds between each image (decimal)'), inputValue: 5, showCancelButton: true, }); @@ -49,7 +49,7 @@ export async function askExtractFramesAsImages({ segmentNumFrames, fps }) { text: i18n.t('Capture exactly one image every nth frame'), icon: 'question', input: 'number', - inputLabel: i18n.t('Enter an integer number of frames'), + inputLabel: i18n.t('Enter the number of frames between each image (integer)'), inputValue: 30, showCancelButton: true, }); @@ -62,7 +62,7 @@ export async function askExtractFramesAsImages({ segmentNumFrames, fps }) { text: i18n.t('Capture exactly one image every nth second'), icon: 'question', input: 'text', - inputLabel: i18n.t('Enter a decimal number of seconds'), + inputLabel: i18n.t('Enter the number of seconds between each image (decimal)'), inputValue: 5, showCancelButton: true, }); diff --git a/src/ffmpeg.js b/src/ffmpeg.js index b1e1f4be..858fed68 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -18,6 +18,9 @@ const { pathExists } = window.require('fs-extra'); let customFfPath; +const runningFfmpegs = new Set(); +// setInterval(() => console.log(runningFfmpegs.size), 1000); + export class RefuseOverwriteError extends Error {} @@ -60,7 +63,23 @@ export async function runFfprobe(args, { timeout = isDev ? 10000 : 30000 } = {}) export function runFfmpeg(args, execaOptions, { logCli = true } = {}) { const ffmpegPath = getFfmpegPath(); if (logCli) console.log(getFfCommandLine('ffmpeg', args)); - return execa(ffmpegPath, args, execaOptions); + const process = execa(ffmpegPath, args, execaOptions); + + (async () => { + runningFfmpegs.add(process); + try { + await process; + } finally { + runningFfmpegs.delete(process); + } + })(); + return process; +} + +export function abortFfmpegs() { + runningFfmpegs.forEach((process) => { + process.kill('SIGTERM', { forceKillAfterTimeout: 10000 }); + }); } export function handleProgress(process, durationIn, onProgress, customMatcher = () => {}) {