mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-25 11:43:17 +01:00
parent
b20596e53a
commit
75f5d3d1ba
27
src/App.jsx
27
src/App.jsx
@ -67,7 +67,7 @@ import {
|
||||
getOutPath, getSuffixedOutPath, handleError, getOutDir,
|
||||
isMasBuild, isStoreBuild, dragPreventer,
|
||||
havePermissionToReadFile, resolvePathIfNeeded, getPathReadAccessError, html5ifiedPrefix, html5dummySuffix, findExistingHtml5FriendlyFile,
|
||||
deleteFiles, isOutOfSpaceError, isExecaFailure, readFileSize, readFileSizes, checkFileSizes, setDocumentTitle,
|
||||
deleteFiles, isOutOfSpaceError, isExecaFailure, readFileSize, readFileSizes, checkFileSizes, setDocumentTitle, getOutFileExtension, getSuffixedFileName,
|
||||
} from './util';
|
||||
import { toast, errorToast } from './swal';
|
||||
import { formatDuration } from './util/duration';
|
||||
@ -148,6 +148,7 @@ const App = memo(() => {
|
||||
const [hideCanvasPreview, setHideCanvasPreview] = useState(false);
|
||||
const [exportConfirmVisible, setExportConfirmVisible] = useState(false);
|
||||
const [cacheBuster, setCacheBuster] = useState(0);
|
||||
const [customMergedOutFileName, setMergedOutFileName] = useState();
|
||||
|
||||
const { fileFormat, setFileFormat, detectedFileFormat, setDetectedFileFormat, isCustomFormatSelected } = useFileFormatState();
|
||||
|
||||
@ -731,6 +732,7 @@ const App = memo(() => {
|
||||
setActiveSubtitleStreamIndex();
|
||||
setHideCanvasPreview(false);
|
||||
setExportConfirmVisible(false);
|
||||
setMergedOutFileName();
|
||||
|
||||
cancelRenderThumbnails();
|
||||
}, [cutSegmentsHistory, clearSegments, setFileFormat, setDetectedFileFormat, setDeselectedSegmentIds, cancelRenderThumbnails]);
|
||||
@ -983,7 +985,7 @@ const App = memo(() => {
|
||||
if (sendErrorReport) openSendConcatReportDialogWithState(err, reportState);
|
||||
}, [fileFormat, openSendConcatReportDialogWithState]);
|
||||
|
||||
const userConcatFiles = useCallback(async ({ paths, includeAllStreams, streams, fileFormat: outFormat, fileName: outFileName, clearBatchFilesAfterConcat }) => {
|
||||
const userConcatFiles = useCallback(async ({ paths, includeAllStreams, streams, fileFormat: outFormat, outFileName, clearBatchFilesAfterConcat }) => {
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setConcatDialogVisible(false);
|
||||
@ -1109,6 +1111,16 @@ const App = memo(() => {
|
||||
|
||||
const willMerge = segmentsToExport.length > 1 && autoMerge;
|
||||
|
||||
const mergedOutFileName = useMemo(() => {
|
||||
if (customMergedOutFileName != null) return customMergedOutFileName;
|
||||
const ext = getOutFileExtension({ isCustomFormatSelected, outFormat: fileFormat, filePath });
|
||||
return getSuffixedFileName(filePath, `cut-merged-${new Date().getTime()}${ext}`);
|
||||
}, [customMergedOutFileName, fileFormat, filePath, isCustomFormatSelected]);
|
||||
|
||||
const mergedOutFilePath = useMemo(() => (
|
||||
getOutPath({ customOutDir, filePath, fileName: mergedOutFileName })
|
||||
), [customOutDir, filePath, mergedOutFileName]);
|
||||
|
||||
const onExportConfirm = useCallback(async () => {
|
||||
if (numStreamsToCopy === 0) {
|
||||
errorToast(i18n.t('No tracks selected for export'));
|
||||
@ -1167,17 +1179,15 @@ const App = memo(() => {
|
||||
detectedFps,
|
||||
});
|
||||
|
||||
let concatOutPath;
|
||||
if (willMerge) {
|
||||
setCutProgress(0);
|
||||
setWorking(i18n.t('Merging'));
|
||||
|
||||
const chapterNames = segmentsToChapters && !invertCutSegments ? segmentsToExport.map((s) => s.name) : undefined;
|
||||
|
||||
concatOutPath = await autoConcatCutSegments({
|
||||
await autoConcatCutSegments({
|
||||
customOutDir,
|
||||
outFormat: fileFormat,
|
||||
isCustomFormatSelected,
|
||||
segmentPaths: outFiles,
|
||||
ffmpegExperimental,
|
||||
preserveMovData,
|
||||
@ -1187,6 +1197,7 @@ const App = memo(() => {
|
||||
autoDeleteMergedSegments,
|
||||
preserveMetadataOnMerge,
|
||||
appendFfmpegCommandLog,
|
||||
mergedOutFilePath,
|
||||
});
|
||||
}
|
||||
|
||||
@ -1217,7 +1228,7 @@ const App = memo(() => {
|
||||
|
||||
if (areWeCutting) notices.push(i18n.t('Cutpoints may be inaccurate.'));
|
||||
|
||||
const revealPath = concatOutPath || outFiles[0];
|
||||
const revealPath = willMerge ? mergedOutFilePath : outFiles[0];
|
||||
if (!hideAllNotifications) openExportFinishedToast({ filePath: revealPath, warnings, notices });
|
||||
|
||||
if (cleanupChoices.cleanupAfterExport) await cleanupFiles(cleanupChoices);
|
||||
@ -1244,7 +1255,7 @@ const App = memo(() => {
|
||||
setWorking();
|
||||
setCutProgress();
|
||||
}
|
||||
}, [numStreamsToCopy, setWorking, segmentsToChaptersOnly, outSegTemplateOrDefault, generateOutSegFileNames, segmentsToExport, getOutSegError, cutMultiple, outputDir, customOutDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, preserveMetadataOnMerge, movFastStart, avoidNegativeTs, customTagsByFile, paramsByStreamId, detectedFps, willMerge, enableOverwriteOutput, exportConfirmEnabled, mainFileFormatData, mainStreams, exportExtraStreams, areWeCutting, hideAllNotifications, cleanupChoices, cleanupFiles, selectedSegmentsOrInverse, segmentsToChapters, invertCutSegments, autoConcatCutSegments, isCustomFormatSelected, autoDeleteMergedSegments, nonCopiedExtraStreams, filePath, handleExportFailed]);
|
||||
}, [numStreamsToCopy, setWorking, segmentsToChaptersOnly, outSegTemplateOrDefault, generateOutSegFileNames, segmentsToExport, getOutSegError, cutMultiple, outputDir, customOutDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, preserveMetadataOnMerge, movFastStart, avoidNegativeTs, customTagsByFile, paramsByStreamId, detectedFps, willMerge, enableOverwriteOutput, exportConfirmEnabled, mainFileFormatData, mainStreams, exportExtraStreams, areWeCutting, mergedOutFilePath, hideAllNotifications, cleanupChoices, cleanupFiles, selectedSegmentsOrInverse, segmentsToChapters, invertCutSegments, autoConcatCutSegments, autoDeleteMergedSegments, nonCopiedExtraStreams, filePath, handleExportFailed]);
|
||||
|
||||
const onExportPress = useCallback(async () => {
|
||||
if (!filePath || workingRef.current || segmentsToExport.length < 1) return;
|
||||
@ -2483,7 +2494,7 @@ const App = memo(() => {
|
||||
)}
|
||||
</Sheet>
|
||||
|
||||
<ExportConfirm filePath={filePath} areWeCutting={areWeCutting} nonFilteredSegmentsOrInverse={nonFilteredSegmentsOrInverse} selectedSegments={selectedSegmentsOrInverse} segmentsToExport={segmentsToExport} willMerge={willMerge} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} renderOutFmt={renderOutFmt} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} onShowStreamsSelectorClick={handleShowStreamsSelectorClick} outFormat={fileFormat} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} getOutSegError={getOutSegError} mainCopiedThumbnailStreams={mainCopiedThumbnailStreams} needSmartCut={needSmartCut} />
|
||||
<ExportConfirm filePath={filePath} areWeCutting={areWeCutting} nonFilteredSegmentsOrInverse={nonFilteredSegmentsOrInverse} selectedSegments={selectedSegmentsOrInverse} segmentsToExport={segmentsToExport} willMerge={willMerge} visible={exportConfirmVisible} onClosePress={closeExportConfirm} onExportConfirm={onExportConfirm} renderOutFmt={renderOutFmt} outputDir={outputDir} numStreamsTotal={numStreamsTotal} numStreamsToCopy={numStreamsToCopy} onShowStreamsSelectorClick={handleShowStreamsSelectorClick} outFormat={fileFormat} setOutSegTemplate={setOutSegTemplate} outSegTemplate={outSegTemplateOrDefault} generateOutSegFileNames={generateOutSegFileNames} currentSegIndexSafe={currentSegIndexSafe} getOutSegError={getOutSegError} mainCopiedThumbnailStreams={mainCopiedThumbnailStreams} needSmartCut={needSmartCut} mergedOutFileName={mergedOutFileName} setMergedOutFileName={setMergedOutFileName} />
|
||||
|
||||
<LastCommandsSheet
|
||||
visible={lastCommandsVisible}
|
||||
|
@ -155,7 +155,7 @@ const ConcatDialog = memo(({
|
||||
|
||||
const onOutputFormatUserChange = useCallback((newFormat) => setFileFormat(newFormat), [setFileFormat]);
|
||||
|
||||
const onConcatClick = useCallback(() => onConcat({ paths, includeAllStreams, streams: fileMeta.streams, fileName: outFileName, fileFormat, clearBatchFilesAfterConcat }), [clearBatchFilesAfterConcat, fileFormat, fileMeta, includeAllStreams, onConcat, outFileName, paths]);
|
||||
const onConcatClick = useCallback(() => onConcat({ paths, includeAllStreams, streams: fileMeta.streams, outFileName, fileFormat, clearBatchFilesAfterConcat }), [clearBatchFilesAfterConcat, fileFormat, fileMeta, includeAllStreams, onConcat, outFileName, paths]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -15,6 +15,7 @@ import OutSegTemplateEditor from './OutSegTemplateEditor';
|
||||
import HighlightedText, { highlightedTextStyle } from './HighlightedText';
|
||||
import Select from './Select';
|
||||
import Switch from './Switch';
|
||||
import MergedOutFileName from './MergedOutFileName';
|
||||
|
||||
import { primaryTextColor } from '../colors';
|
||||
import { withBlur } from '../util';
|
||||
@ -36,7 +37,7 @@ const ExportConfirm = memo(({
|
||||
areWeCutting, selectedSegments, segmentsToExport, willMerge, visible, onClosePress, onExportConfirm,
|
||||
outFormat, renderOutFmt, outputDir, numStreamsTotal, numStreamsToCopy, onShowStreamsSelectorClick, outSegTemplate,
|
||||
setOutSegTemplate, generateOutSegFileNames, filePath, currentSegIndexSafe, getOutSegError, nonFilteredSegmentsOrInverse,
|
||||
mainCopiedThumbnailStreams, needSmartCut,
|
||||
mainCopiedThumbnailStreams, needSmartCut, mergedOutFileName, setMergedOutFileName,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -198,6 +199,20 @@ const ExportConfirm = memo(({
|
||||
</tr>
|
||||
)}
|
||||
|
||||
{willMerge && (
|
||||
<tr>
|
||||
<td>
|
||||
{t('Merged output file name:')}
|
||||
</td>
|
||||
<td>
|
||||
<MergedOutFileName mergedOutFileName={mergedOutFileName} setMergedOutFileName={setMergedOutFileName} />
|
||||
</td>
|
||||
<td>
|
||||
<HelpIcon onClick={() => showHelpText({ text: t('Name of the merged/concatenated output file when concatenating multiple segments.') })} />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
{t('Overwrite existing files')}
|
||||
|
12
src/components/MergedOutFileName.jsx
Normal file
12
src/components/MergedOutFileName.jsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import TextInput from './TextInput';
|
||||
|
||||
|
||||
const MergedOutFileName = memo(({ mergedOutFileName, setMergedOutFileName }) => (
|
||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', justifyContent: 'flex-end' }}>
|
||||
<TextInput value={mergedOutFileName} onChange={(e) => setMergedOutFileName(e.target.value)} style={{ textAlign: 'right' }} />
|
||||
</div>
|
||||
));
|
||||
|
||||
export default MergedOutFileName;
|
@ -13,6 +13,7 @@ import { defaultOutSegTemplate, segNumVariable, segSuffixVariable } from '../uti
|
||||
import useUserSettings from '../hooks/useUserSettings';
|
||||
import Switch from './Switch';
|
||||
import Select from './Select';
|
||||
import TextInput from './TextInput';
|
||||
|
||||
const ReactSwal = withReactContent(Swal);
|
||||
|
||||
@ -22,8 +23,6 @@ const formatVariable = (variable) => `\${${variable}}`;
|
||||
|
||||
const extVar = formatVariable('EXT');
|
||||
|
||||
const inputStyle = { flexGrow: 1, fontFamily: 'inherit', fontSize: '.8em', backgroundColor: 'var(--gray3)', color: 'var(--gray12)', border: '1px solid var(--gray7)', appearance: 'none' };
|
||||
|
||||
const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generateOutSegFileNames, currentSegIndexSafe, getOutSegError }) => {
|
||||
const { safeOutputFileName, toggleSafeOutputFileName, outputFileNameMinZeroPadding, setOutputFileNameMinZeroPadding } = useUserSettings();
|
||||
|
||||
@ -117,7 +116,7 @@ const OutSegTemplateEditor = memo(({ outSegTemplate, setOutSegTemplate, generate
|
||||
exit={{ opacity: 0, height: 0, marginTop: 0 }}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', marginBottom: '.2em' }}>
|
||||
<input type="text" ref={inputRef} style={inputStyle} onChange={onTextChange} value={text} autoComplete="off" autoCapitalize="off" autoCorrect="off" />
|
||||
<TextInput ref={inputRef} onChange={onTextChange} value={text} autoComplete="off" autoCapitalize="off" autoCorrect="off" />
|
||||
|
||||
{outSegFileNames != null && <Button height={20} onClick={onAllSegmentsPreviewPress} marginLeft={5}>{t('Preview')}</Button>}
|
||||
|
||||
|
10
src/components/TextInput.jsx
Normal file
10
src/components/TextInput.jsx
Normal file
@ -0,0 +1,10 @@
|
||||
import React, { forwardRef } from 'react';
|
||||
|
||||
const inputStyle = { borderRadius: '.4em', flexGrow: 1, fontFamily: 'inherit', fontSize: '.8em', backgroundColor: 'var(--gray3)', color: 'var(--gray12)', border: '1px solid var(--gray7)', appearance: 'none' };
|
||||
|
||||
const TextInput = forwardRef(({ style, ...props }, forwardedRef) => (
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
<input type="text" ref={forwardedRef} style={{ ...inputStyle, ...style }} {...props} />
|
||||
));
|
||||
|
||||
export default TextInput;
|
@ -437,9 +437,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
|
||||
}
|
||||
}, [concatFiles, cutSingle, filePath, needSmartCut, shouldSkipExistingFile]);
|
||||
|
||||
const autoConcatCutSegments = useCallback(async ({ customOutDir, isCustomFormatSelected, outFormat, segmentPaths, ffmpegExperimental, onProgress, preserveMovData, movFastStart, autoDeleteMergedSegments, chapterNames, preserveMetadataOnMerge, appendFfmpegCommandLog }) => {
|
||||
const ext = getOutFileExtension({ isCustomFormatSelected, outFormat, filePath });
|
||||
const outPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix: `cut-merged-${new Date().getTime()}${ext}` });
|
||||
const autoConcatCutSegments = useCallback(async ({ customOutDir, outFormat, segmentPaths, ffmpegExperimental, onProgress, preserveMovData, movFastStart, autoDeleteMergedSegments, chapterNames, preserveMetadataOnMerge, appendFfmpegCommandLog, mergedOutFilePath }) => {
|
||||
const outDir = getOutDir(customOutDir, filePath);
|
||||
|
||||
const chapters = await createChaptersFromSegments({ segmentPaths, chapterNames });
|
||||
@ -447,10 +445,8 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea
|
||||
const metadataFromPath = segmentPaths[0];
|
||||
// need to re-read streams because may have changed
|
||||
const { streams } = await readFileMeta(metadataFromPath);
|
||||
await concatFiles({ paths: segmentPaths, outDir, outPath, metadataFromPath, outFormat, includeAllStreams: true, streams, ffmpegExperimental, onProgress, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge, appendFfmpegCommandLog });
|
||||
await concatFiles({ paths: segmentPaths, outDir, outPath: mergedOutFilePath, metadataFromPath, outFormat, includeAllStreams: true, streams, ffmpegExperimental, onProgress, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge, appendFfmpegCommandLog });
|
||||
if (autoDeleteMergedSegments) await tryDeleteFiles(segmentPaths);
|
||||
|
||||
return outPath;
|
||||
}, [concatFiles, filePath]);
|
||||
|
||||
const html5ify = useCallback(async ({ customOutDir, filePath: filePathArg, speed, hasAudio, hasVideo, onProgress }) => {
|
||||
|
Loading…
Reference in New Issue
Block a user