kopia lustrzana https://github.com/mifi/lossless-cut
rodzic
e49bfee750
commit
84535c8f8e
10
src/App.jsx
10
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(() => {
|
|||
)}
|
||||
|
||||
<AnimatePresence>
|
||||
{working && <Loading text={working} cutProgress={cutProgress} />}
|
||||
{working && <Working text={working} cutProgress={cutProgress} onAbortClick={abortFfmpegs} />}
|
||||
</AnimatePresence>
|
||||
|
||||
{tunerVisible && <ValueTuners type={tunerVisible} onFinished={() => setTunerVisible()} />}
|
||||
|
|
|
@ -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 }) => (
|
||||
<div style={{ position: 'absolute', zIndex: 1, bottom: 0, top: 0, left: 0, right: 0, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<motion.div
|
||||
style={{ background: primaryColor, boxShadow: `${primaryColor} 0px 0px 20px 25px`, borderRadius: 20, paddingBottom: 15, color: 'white', textAlign: 'center', fontSize: 14 }}
|
||||
style={{ background: primaryColor, boxShadow: `${primaryColor} 0px 0px 20px 25px`, borderRadius: 60, paddingBottom: 5, color: 'white', fontSize: 14, display: 'flex', flexDirection: 'column', alignItems: 'center' }}
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0 }}
|
||||
>
|
||||
<div style={{ width: 150, height: 150 }}>
|
||||
<div style={{ width: 150, height: 80 }}>
|
||||
<Lottie
|
||||
loop
|
||||
animationData={loadingLottie}
|
||||
play
|
||||
style={{ width: '170%', height: '130%', marginLeft: '-35%', marginTop: '-29%', pointerEvents: 'none' }}
|
||||
style={{ width: '170%', height: '210%', marginLeft: '-40%', marginTop: '-35%', pointerEvents: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: 10, width: 150 }}>
|
||||
<div style={{ marginTop: 5 }}>
|
||||
{text}...
|
||||
</div>
|
||||
|
||||
{(cutProgress != null) && (
|
||||
<div style={{ marginTop: 10 }}>
|
||||
<div style={{ marginTop: 5 }}>
|
||||
{`${(cutProgress * 100).toFixed(1)} %`}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: 5 }}>
|
||||
<Button intent="danger" onClick={onAbortClick} height={20}><Trans>Abort</Trans></Button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
));
|
||||
|
||||
export default Loading;
|
||||
export default Working;
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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 = () => {}) {
|
||||
|
|
Ładowanie…
Reference in New Issue