kopia lustrzana https://github.com/mifi/lossless-cut
rodzic
6c10630996
commit
90126d92b1
75
src/App.jsx
75
src/App.jsx
|
@ -703,43 +703,6 @@ const App = memo(() => {
|
|||
return { cancel: false, newCustomOutDir };
|
||||
}, [customOutDir, setCustomOutDir]);
|
||||
|
||||
const userConcatFiles = useCallback(async ({ paths, includeAllStreams, streams, fileFormat: outFormat, isCustomFormatSelected: isCustomFormatSelected2 }) => {
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setConcatDialogVisible(false);
|
||||
setWorking(i18n.t('Merging'));
|
||||
|
||||
const firstPath = paths[0];
|
||||
const { newCustomOutDir, cancel } = await ensureAccessibleDirectories({ inputPath: firstPath });
|
||||
if (cancel) return;
|
||||
|
||||
const ext = getOutFileExtension({ isCustomFormatSelected: isCustomFormatSelected2, outFormat, filePath: firstPath });
|
||||
const outPath = getOutPath({ customOutDir: newCustomOutDir, filePath: firstPath, nameSuffix: `merged${ext}` });
|
||||
const outDir = getOutDir(customOutDir, firstPath);
|
||||
|
||||
let chaptersFromSegments;
|
||||
if (segmentsToChapters) {
|
||||
const chapterNames = paths.map((path) => parsePath(path).name);
|
||||
chaptersFromSegments = await createChaptersFromSegments({ segmentPaths: paths, chapterNames });
|
||||
}
|
||||
|
||||
// console.log('merge', paths);
|
||||
const metadataFromPath = paths[0];
|
||||
await concatFiles({ paths, outPath, outDir, outFormat, metadataFromPath, includeAllStreams, streams, ffmpegExperimental, onProgress: setCutProgress, preserveMovData, movFastStart, preserveMetadataOnMerge, chapters: chaptersFromSegments, appendFfmpegCommandLog });
|
||||
openDirToast({ icon: 'success', dirPath: outDir, text: i18n.t('Files merged!') });
|
||||
} catch (err) {
|
||||
if (isOutOfSpaceError(err)) {
|
||||
showDiskFull();
|
||||
return;
|
||||
}
|
||||
errorToast(i18n.t('Failed to merge files. Make sure they are all of the exact same codecs'));
|
||||
console.error('Failed to merge files', err);
|
||||
} finally {
|
||||
setWorking();
|
||||
setCutProgress();
|
||||
}
|
||||
}, [setWorking, ensureAccessibleDirectories, customOutDir, segmentsToChapters, concatFiles, ffmpegExperimental, preserveMovData, movFastStart, preserveMetadataOnMerge]);
|
||||
|
||||
const concatCurrentBatch = useCallback(() => {
|
||||
if (batchFiles.length < 2) {
|
||||
errorToast(i18n.t('Please open at least 2 files to merge, then try again'));
|
||||
|
@ -1109,6 +1072,44 @@ const App = memo(() => {
|
|||
});
|
||||
}, []);
|
||||
|
||||
const userConcatFiles = useCallback(async ({ paths, includeAllStreams, streams, fileFormat: outFormat, isCustomFormatSelected: isCustomFormatSelected2, clearBatchFilesAfterConcat }) => {
|
||||
if (workingRef.current) return;
|
||||
try {
|
||||
setConcatDialogVisible(false);
|
||||
setWorking(i18n.t('Merging'));
|
||||
|
||||
const firstPath = paths[0];
|
||||
const { newCustomOutDir, cancel } = await ensureAccessibleDirectories({ inputPath: firstPath });
|
||||
if (cancel) return;
|
||||
|
||||
const ext = getOutFileExtension({ isCustomFormatSelected: isCustomFormatSelected2, outFormat, filePath: firstPath });
|
||||
const outPath = getOutPath({ customOutDir: newCustomOutDir, filePath: firstPath, nameSuffix: `merged${ext}` });
|
||||
const outDir = getOutDir(customOutDir, firstPath);
|
||||
|
||||
let chaptersFromSegments;
|
||||
if (segmentsToChapters) {
|
||||
const chapterNames = paths.map((path) => parsePath(path).name);
|
||||
chaptersFromSegments = await createChaptersFromSegments({ segmentPaths: paths, chapterNames });
|
||||
}
|
||||
|
||||
// console.log('merge', paths);
|
||||
const metadataFromPath = paths[0];
|
||||
await concatFiles({ paths, outPath, outDir, outFormat, metadataFromPath, includeAllStreams, streams, ffmpegExperimental, onProgress: setCutProgress, preserveMovData, movFastStart, preserveMetadataOnMerge, chapters: chaptersFromSegments, appendFfmpegCommandLog });
|
||||
if (clearBatchFilesAfterConcat) closeBatch();
|
||||
openDirToast({ icon: 'success', dirPath: outDir, text: i18n.t('Files merged!') });
|
||||
} catch (err) {
|
||||
if (isOutOfSpaceError(err)) {
|
||||
showDiskFull();
|
||||
return;
|
||||
}
|
||||
errorToast(i18n.t('Failed to merge files. Make sure they are all of the exact same codecs'));
|
||||
console.error('Failed to merge files', err);
|
||||
} finally {
|
||||
setWorking();
|
||||
setCutProgress();
|
||||
}
|
||||
}, [setWorking, ensureAccessibleDirectories, customOutDir, segmentsToChapters, concatFiles, ffmpegExperimental, preserveMovData, movFastStart, preserveMetadataOnMerge, closeBatch]);
|
||||
|
||||
const cleanupFilesDialog = useCallback(async () => {
|
||||
if (!isFileOpened) return;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { memo, useState, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Dialog, Checkbox, Button, Paragraph } from 'evergreen-ui';
|
||||
import { Dialog, Checkbox, Button, Paragraph, CogIcon } from 'evergreen-ui';
|
||||
import { AiOutlineMergeCells } from 'react-icons/ai';
|
||||
|
||||
import { readFileMeta, getSmarterOutFormat } from '../ffmpeg';
|
||||
|
@ -26,6 +26,8 @@ const ConcatDialog = memo(({
|
|||
const [paths, setPaths] = useState(initialPaths);
|
||||
const [includeAllStreams, setIncludeAllStreams] = useState(false);
|
||||
const [fileMeta, setFileMeta] = useState();
|
||||
const [clearBatchFilesAfterConcat, setClearBatchFilesAfterConcat] = useState(false);
|
||||
const [settingsVisible, setSettingsVisible] = useState(false);
|
||||
|
||||
const { fileFormat, setFileFormat, detectedFileFormat, setDetectedFileFormat, isCustomFormatSelected } = useFileFormatState();
|
||||
|
||||
|
@ -60,53 +62,58 @@ const ConcatDialog = memo(({
|
|||
|
||||
const onOutputFormatUserChange = useCallback((newFormat) => setFileFormat(newFormat), [setFileFormat]);
|
||||
|
||||
const onConcatClick = useCallback(() => onConcat({ paths, includeAllStreams, streams: fileMeta.streams, fileFormat, isCustomFormatSelected }), [fileFormat, fileMeta, includeAllStreams, isCustomFormatSelected, onConcat, paths]);
|
||||
const onConcatClick = useCallback(() => onConcat({ paths, includeAllStreams, streams: fileMeta.streams, fileFormat, isCustomFormatSelected, clearBatchFilesAfterConcat }), [clearBatchFilesAfterConcat, fileFormat, fileMeta, includeAllStreams, isCustomFormatSelected, onConcat, paths]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={t('Merge/concatenate files')}
|
||||
isShown={isShown}
|
||||
onCloseComplete={onHide}
|
||||
topOffset="3vh"
|
||||
width="90vw"
|
||||
footer={(
|
||||
<>
|
||||
{fileFormat && detectedFileFormat && <OutputFormatSelect style={{ maxWidth: 150 }} detectedFileFormat={detectedFileFormat} fileFormat={fileFormat} onOutputFormatUserChange={onOutputFormatUserChange} />}
|
||||
<Button onClick={onHide} style={{ marginLeft: 10 }}>Cancel</Button>
|
||||
<Button iconBefore={<AiOutlineMergeCells />} isLoading={detectedFileFormat == null} appearance="primary" onClick={onConcatClick}>{t('Merge!')}</Button>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<div style={containerStyle}>
|
||||
<div style={{ whiteSpace: 'pre-wrap', fontSize: 14, marginBottom: 10 }}>
|
||||
{t('This dialog can be used to concatenate files in series, e.g. one after the other:\n[file1][file2][file3]\nIt can NOT be used for merging tracks in parallell (like adding an audio track to a video).\nMake sure all files are of the exact same codecs & codec parameters (fps, resolution etc).')}
|
||||
<>
|
||||
<Dialog
|
||||
title={t('Merge/concatenate files')}
|
||||
isShown={isShown}
|
||||
onCloseComplete={onHide}
|
||||
topOffset="3vh"
|
||||
width="90vw"
|
||||
footer={(
|
||||
<>
|
||||
<Button iconBefore={CogIcon} onClick={() => setSettingsVisible(true)}>{t('Options')}</Button>
|
||||
{fileFormat && detectedFileFormat && <OutputFormatSelect style={{ maxWidth: 180 }} detectedFileFormat={detectedFileFormat} fileFormat={fileFormat} onOutputFormatUserChange={onOutputFormatUserChange} />}
|
||||
<Button onClick={onHide} style={{ marginLeft: 10 }}>Cancel</Button>
|
||||
<Button iconBefore={<AiOutlineMergeCells />} isLoading={detectedFileFormat == null} appearance="primary" onClick={onConcatClick}>{t('Merge!')}</Button>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<div style={containerStyle}>
|
||||
<div style={{ whiteSpace: 'pre-wrap', fontSize: 14, marginBottom: 10 }}>
|
||||
{t('This dialog can be used to concatenate files in series, e.g. one after the other:\n[file1][file2][file3]\nIt can NOT be used for merging tracks in parallell (like adding an audio track to a video).\nMake sure all files are of the exact same codecs & codec parameters (fps, resolution etc).')}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{paths.map((path, index) => (
|
||||
<div key={path} style={rowStyle} title={path}>
|
||||
{index + 1}
|
||||
{'. '}
|
||||
<span style={{ color: 'rgba(0,0,0,0.7)' }}>{basename(path)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
|
||||
<div style={{ marginTop: 10, marginBottom: 10 }}>
|
||||
<Checkbox checked={includeAllStreams} onChange={(e) => setIncludeAllStreams(e.target.checked)} label={`${t('Include all tracks?')} ${t('If this is checked, all audio/video/subtitle/data tracks will be included. This may not always work for all file types. If not checked, only default streams will be included.')}`} />
|
||||
<Dialog isShown={settingsVisible} onCloseComplete={() => setSettingsVisible(false)} title={t('Options')} hasCancel={false} confirmLabel={t('Close')}>
|
||||
<Checkbox checked={includeAllStreams} onChange={(e) => setIncludeAllStreams(e.target.checked)} label={`${t('Include all tracks?')} ${t('If this is checked, all audio/video/subtitle/data tracks will be included. This may not always work for all file types. If not checked, only default streams will be included.')}`} />
|
||||
|
||||
<Checkbox checked={preserveMetadataOnMerge} onChange={(e) => setPreserveMetadataOnMerge(e.target.checked)} label={t('Preserve original metadata when merging? (slow)')} />
|
||||
<Checkbox checked={preserveMetadataOnMerge} onChange={(e) => setPreserveMetadataOnMerge(e.target.checked)} label={t('Preserve original metadata when merging? (slow)')} />
|
||||
|
||||
<Checkbox checked={preserveMovData} onChange={(e) => setPreserveMovData(e.target.checked)} label={t('Preserve all MP4/MOV metadata?')} />
|
||||
<Checkbox checked={preserveMovData} onChange={(e) => setPreserveMovData(e.target.checked)} label={t('Preserve all MP4/MOV metadata?')} />
|
||||
|
||||
<Checkbox checked={segmentsToChapters} onChange={(e) => setSegmentsToChapters(e.target.checked)} label={t('Create chapters from merged segments? (slow)')} />
|
||||
<Checkbox checked={segmentsToChapters} onChange={(e) => setSegmentsToChapters(e.target.checked)} label={t('Create chapters from merged segments? (slow)')} />
|
||||
|
||||
<Checkbox checked={alwaysConcatMultipleFiles} onChange={(e) => setAlwaysConcatMultipleFiles(e.target.checked)} label={t('Always open this dialog when opening multiple files')} />
|
||||
<Checkbox checked={alwaysConcatMultipleFiles} onChange={(e) => setAlwaysConcatMultipleFiles(e.target.checked)} label={t('Always open this dialog when opening multiple files')} />
|
||||
|
||||
<Paragraph>{t('Note that also other settings from the normal export dialog apply to this merge function. For more information about all options, see the export dialog.')}</Paragraph>
|
||||
</div>
|
||||
<Checkbox checked={clearBatchFilesAfterConcat} onChange={(e) => setClearBatchFilesAfterConcat(e.target.checked)} label={t('Clear batch file list after merge')} />
|
||||
|
||||
<div>
|
||||
{paths.map((path, index) => (
|
||||
<div style={rowStyle} title={path}>
|
||||
{index + 1}
|
||||
{'. '}
|
||||
<span style={{ color: 'rgba(0,0,0,0.7)' }}>{basename(path)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
<Paragraph>{t('Note that also other settings from the normal export dialog apply to this merge function. For more information about all options, see the export dialog.')}</Paragraph>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue