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:
parent
b4641aeb22
commit
5fe8c088e0
24
src/App.jsx
24
src/App.jsx
@ -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), []);
|
||||
|
@ -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.')}`} />
|
||||
|
||||
|
10
src/hooks/useFileFormatState.js
Normal file
10
src/hooks/useFileFormatState.js
Normal 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 };
|
||||
};
|
Loading…
Reference in New Issue
Block a user