1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-26 12:12:39 +01:00

improve format detection in concat/merge dialog

and allow selecting any output format

fixes #1032
This commit is contained in:
Mikael Finstad 2022-02-23 21:22:29 +08:00
parent b4641aeb22
commit 5fe8c088e0
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
3 changed files with 56 additions and 19 deletions

View File

@ -23,6 +23,8 @@ import useFfmpegOperations from './hooks/useFfmpegOperations';
import useKeyframes from './hooks/useKeyframes';
import useWaveform from './hooks/useWaveform';
import useKeyboard from './hooks/useKeyboard';
import useFileFormatState from './hooks/useFileFormatState';
import NoFileLoaded from './NoFileLoaded';
import Canvas from './Canvas';
import TopMenu from './TopMenu';
@ -75,7 +77,7 @@ const isDev = window.require('electron-is-dev');
const electron = window.require('electron'); // eslint-disable-line
const { exists } = window.require('fs-extra');
const filePathToUrl = window.require('file-url');
const { extname, parse: parsePath, join: pathJoin, basename, dirname } = window.require('path');
const { parse: parsePath, join: pathJoin, basename, dirname } = window.require('path');
const { dialog } = electron.remote;
@ -107,10 +109,8 @@ const App = memo(() => {
const [playing, setPlaying] = useState(false);
const [playerTime, setPlayerTime] = useState();
const [duration, setDuration] = useState();
const [fileFormat, setFileFormat] = useState();
const [fileFormatData, setFileFormatData] = useState();
const [chapters, setChapters] = useState();
const [detectedFileFormat, setDetectedFileFormat] = useState();
const [rotation, setRotation] = useState(360);
const [cutProgress, setCutProgress] = useState();
const [startTimeOffset, setStartTimeOffset] = useState(0);
@ -136,6 +136,8 @@ const App = memo(() => {
const [hideCanvasPreview, setHideCanvasPreview] = useState(false);
const [exportConfirmVisible, setExportConfirmVisible] = useState(false);
const { fileFormat, setFileFormat, detectedFileFormat, setDetectedFileFormat, isCustomFormatSelected } = useFileFormatState();
// State per application launch
const [keyframesEnabled, setKeyframesEnabled] = useState(true);
const [waveformEnabled, setWaveformEnabled] = useState(false);
@ -192,8 +194,6 @@ const App = memo(() => {
const durationSafe = isDurationValid(duration) ? duration : 1;
const zoomedDuration = isDurationValid(duration) ? duration / zoom : undefined;
const isCustomFormatSelected = fileFormat !== detectedFileFormat;
const {
captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, setAvoidNegativeTs, autoMerge, setAutoMerge, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, setAutoExportExtraStreams, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, setAutoSaveProjectFile, wheelSensitivity, setWheelSensitivity, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, autoDeleteMergedSegments, setAutoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, simpleMode, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, setKeyboardSeekAccFactor, keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed, enableTransferTimestamps, setEnableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, setEnableAutoHtml5ify, segmentsToChaptersOnly, setSegmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings,
} = useUserPreferences();
@ -221,7 +221,7 @@ const App = memo(() => {
if (outFormatLocked) {
setOutFormatLocked(newFormat === detectedFileFormat ? undefined : newFormat);
}
}, [detectedFileFormat, outFormatLocked, setOutFormatLocked]);
}, [detectedFileFormat, outFormatLocked, setFileFormat, setOutFormatLocked]);
const setTimelineMode = useCallback((newMode) => {
if (newMode === 'waveform') {
@ -674,7 +674,7 @@ const App = memo(() => {
return { cancel: false, newCustomOutDir };
}, [customOutDir, setCustomOutDir]);
const userConcatFiles = useCallback(async ({ paths, allStreams }) => {
const userConcatFiles = useCallback(async ({ paths, allStreams, fileFormat: fileFormat2, isCustomFormatSelected: isCustomFormatSelected2 }) => {
if (workingRef.current) return;
try {
setConcatDialogVisible(false);
@ -684,12 +684,10 @@ const App = memo(() => {
const { newCustomOutDir, cancel } = await ensureAccessibleDirectories({ inputPath: firstPath });
if (cancel) return;
const ext = extname(firstPath);
const ext = getOutFileExtension({ isCustomFormatSelected: isCustomFormatSelected2, outFormat: fileFormat2, filePath: firstPath });
const outPath = getOutPath({ customOutDir: newCustomOutDir, filePath: firstPath, nameSuffix: `merged${ext}` });
const outDir = getOutDir(customOutDir, firstPath);
const fileMeta = await readFileMeta(firstPath);
let chaptersFromSegments;
if (segmentsToChapters) {
const chapterNames = paths.map((path) => parsePath(path).name);
@ -697,7 +695,7 @@ const App = memo(() => {
}
// console.log('merge', paths);
await ffmpegMergeFiles({ paths, outPath, outDir, fileFormat: fileMeta.fileFormat, allStreams, ffmpegExperimental, onProgress: setCutProgress, preserveMovData, movFastStart, preserveMetadataOnMerge, chapters: chaptersFromSegments });
await ffmpegMergeFiles({ paths, outPath, outDir, fileFormat: fileFormat2, allStreams, ffmpegExperimental, onProgress: setCutProgress, preserveMovData, movFastStart, preserveMetadataOnMerge, chapters: chaptersFromSegments });
openDirToast({ icon: 'success', dirPath: outDir, text: i18n.t('Files merged!') });
} catch (err) {
if (isOutOfSpaceError(err)) {
@ -919,7 +917,7 @@ const App = memo(() => {
cancelRenderThumbnails();
});
}, [cutSegmentsHistory, clearSegments, cancelRenderThumbnails]);
}, [cutSegmentsHistory, clearSegments, setFileFormat, setDetectedFileFormat, cancelRenderThumbnails]);
const showUnsupportedFileMessage = useCallback(() => {
@ -1496,7 +1494,7 @@ const App = memo(() => {
resetState();
throw err;
}
}, [resetState, html5ifyAndLoadWithPreferences, loadEdlFile, getEdlFilePath, getEdlFilePathOld, loadCutSegments, enableAskForImportChapters, autoLoadTimecode, outFormatLocked, showPreviewFileLoadedMessage, setWorking, setCopyStreamIdsForPath]);
}, [resetState, setWorking, showPreviewFileLoadedMessage, autoLoadTimecode, html5ifyAndLoadWithPreferences, getEdlFilePath, getEdlFilePathOld, loadEdlFile, enableAskForImportChapters, loadCutSegments, setCopyStreamIdsForPath, setFileFormat, outFormatLocked, setDetectedFileFormat]);
const toggleHelp = useCallback(() => setHelpVisible(val => !val), []);
const toggleSettings = useCallback(() => setSettingsVisible(val => !val), []);

View File

@ -3,6 +3,11 @@ import { useTranslation } from 'react-i18next';
import { Dialog, Pane, Checkbox, SortAlphabeticalIcon, SortAlphabeticalDescIcon, Button, Paragraph } from 'evergreen-ui';
import { sortableContainer, sortableElement } from 'react-sortable-hoc';
import arrayMove from 'array-move';
import { AiOutlineMergeCells } from 'react-icons/ai';
import { readFileMeta } from '../ffmpeg';
import useFileFormatState from '../hooks/useFileFormatState';
import OutputFormatSelect from './OutputFormatSelect';
const { basename } = window.require('path');
@ -41,9 +46,27 @@ const ConcatDialog = memo(({
const [allStreams, setAllStreams] = useState(false);
const [sortDesc, setSortDesc] = useState();
const { fileFormat, setFileFormat, detectedFileFormat, setDetectedFileFormat, isCustomFormatSelected } = useFileFormatState();
useEffect(() => {
setPaths(initialPaths);
}, [initialPaths]);
if (initialPaths.length === 0) return undefined;
let aborted = false;
(async () => {
const firstPath = initialPaths[0];
setFileFormat();
setDetectedFileFormat();
const fileMeta = await readFileMeta(firstPath);
if (aborted) return;
setFileFormat(fileMeta.fileFormat);
setDetectedFileFormat(fileMeta.fileFormat);
})().catch(console.error);
return () => {
aborted = true;
};
}, [initialPaths, setDetectedFileFormat, setFileFormat]);
const onSortEnd = useCallback(({ oldIndex, newIndex }) => {
const newPaths = arrayMove(paths, oldIndex, newIndex);
@ -60,16 +83,24 @@ const ConcatDialog = memo(({
setSortDesc(newSortDesc);
}, [paths, sortDesc]);
const onOutputFormatUserChange = useCallback((newFormat) => setFileFormat(newFormat), [setFileFormat]);
return (
<Dialog
title={t('Merge/concatenate files')}
isShown={isShown}
confirmLabel={t('Merge!')}
cancelLabel={t('Cancel')}
onCloseComplete={onHide}
onConfirm={() => onConcat({ paths, allStreams })}
onConfirm={() => onConcat({ paths, allStreams, fileFormat, isCustomFormatSelected })}
topOffset="3vh"
width="90vw"
footer={(
<>
{fileFormat && detectedFileFormat && <OutputFormatSelect style={{ maxWidth: 150 }} detectedFileFormat={detectedFileFormat} fileFormat={fileFormat} onOutputFormatUserChange={onOutputFormatUserChange} />}
<Button iconBefore={sortDesc ? SortAlphabeticalDescIcon : SortAlphabeticalIcon} onClick={onSortClick}>{t('Sort items')}</Button>
<Button onClick={onHide} style={{ marginLeft: 10 }}>Cancel</Button>
<Button iconBefore={<AiOutlineMergeCells />} isLoading={detectedFileFormat == null} appearance="primary" onClick={() => onConcat({ paths, allStreams, fileFormat, isCustomFormatSelected })}>{t('Merge!')}</Button>
</>
)}
>
<div style={containerStyle}>
<div style={{ whiteSpace: 'pre-wrap', fontSize: 14, marginBottom: 10 }}>
@ -82,8 +113,6 @@ const ConcatDialog = memo(({
helperClass="dragging-helper-class"
/>
<Button iconBefore={sortDesc ? SortAlphabeticalDescIcon : SortAlphabeticalIcon} onClick={onSortClick}>{t('Sort items')}</Button>
<div style={{ marginTop: 10 }}>
<Checkbox checked={allStreams} onChange={(e) => setAllStreams(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.')}`} />

View File

@ -0,0 +1,10 @@
import { useState } from 'react';
export default () => {
const [detectedFileFormat, setDetectedFileFormat] = useState();
const [fileFormat, setFileFormat] = useState();
const isCustomFormatSelected = fileFormat !== detectedFileFormat;
return { fileFormat, setFileFormat, detectedFileFormat, setDetectedFileFormat, isCustomFormatSelected };
};