add a setting for overwriting output file

fixes #916
pull/1286/head
Mikael Finstad 2022-08-12 20:54:33 +02:00
rodzic 9a2e21a178
commit 9161278e54
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 25AB36E3E81CBC26
7 zmienionych plików z 87 dodań i 26 usunięć

Wyświetl plik

@ -110,6 +110,7 @@ const defaults = {
keyBindings: defaultKeyBindings, keyBindings: defaultKeyBindings,
customFfPath: undefined, customFfPath: undefined,
storeProjectInWorkingDir: true, storeProjectInWorkingDir: true,
enableOverwriteOutput: true,
}; };
// For portable app: https://github.com/mifi/lossless-cut/issues/645 // For portable app: https://github.com/mifi/lossless-cut/issues/645

Wyświetl plik

@ -56,7 +56,7 @@ import {
extractStreams, runStartupCheck, setCustomFfPath as ffmpegSetCustomFfPath, extractStreams, runStartupCheck, setCustomFfPath as ffmpegSetCustomFfPath,
isIphoneHevc, tryMapChaptersToEdl, blackDetect, isIphoneHevc, tryMapChaptersToEdl, blackDetect,
getDuration, getTimecodeFromStreams, createChaptersFromSegments, extractSubtitleTrack, getDuration, getTimecodeFromStreams, createChaptersFromSegments, extractSubtitleTrack,
getFfmpegPath, getFfmpegPath, RefuseOverwriteError,
} from './ffmpeg'; } from './ffmpeg';
import { shouldCopyStreamByDefault, getAudioStreams, getRealVideoStreams, defaultProcessedCodecTypes, isAudioDefinitelyNotSupported, doesPlayerSupportFile } from './util/streams'; import { shouldCopyStreamByDefault, getAudioStreams, getRealVideoStreams, defaultProcessedCodecTypes, isAudioDefinitelyNotSupported, doesPlayerSupportFile } from './util/streams';
import { exportEdlFile, readEdlFile, saveLlcProject, loadLlcProject, askForEdlImport } from './edlStore'; import { exportEdlFile, readEdlFile, saveLlcProject, loadLlcProject, askForEdlImport } from './edlStore';
@ -70,7 +70,7 @@ import {
} from './util'; } from './util';
import { formatDuration } from './util/duration'; import { formatDuration } from './util/duration';
import { adjustRate } from './util/rate-calculator'; import { adjustRate } from './util/rate-calculator';
import { askForOutDir, askForInputDir, askForImportChapters, createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, promptTimeOffset, askForHtml5ifySpeed, askForFileOpenAction, confirmExtractAllStreamsDialog, showCleanupFilesDialog, showDiskFull, showCutFailedDialog, labelSegmentDialog, openYouTubeChaptersDialog, openAbout, showEditableJsonDialog, askForShiftSegments, selectSegmentsByLabelDialog, confirmExtractFramesAsImages } from './dialogs'; import { askForOutDir, askForInputDir, askForImportChapters, createNumSegments as createNumSegmentsDialog, createFixedDurationSegments as createFixedDurationSegmentsDialog, createRandomSegments as createRandomSegmentsDialog, promptTimeOffset, askForHtml5ifySpeed, askForFileOpenAction, confirmExtractAllStreamsDialog, showCleanupFilesDialog, showDiskFull, showCutFailedDialog, labelSegmentDialog, openYouTubeChaptersDialog, openAbout, showEditableJsonDialog, askForShiftSegments, selectSegmentsByLabelDialog, confirmExtractFramesAsImages, showRefuseToOverwrite } from './dialogs';
import { openSendReportDialog } from './reporting'; import { openSendReportDialog } from './reporting';
import { fallbackLng } from './i18n'; import { fallbackLng } from './i18n';
import { createSegment, getCleanCutSegments, getSegApparentStart, findSegmentsAtCursor, sortSegments, invertSegments, getSegmentTags, convertSegmentsToChapters, hasAnySegmentOverlap } from './segments'; import { createSegment, getCleanCutSegments, getSegApparentStart, findSegmentsAtCursor, sortSegments, invertSegments, getSegmentTags, convertSegmentsToChapters, hasAnySegmentOverlap } from './segments';
@ -200,7 +200,7 @@ const App = memo(() => {
const zoomedDuration = isDurationValid(duration) ? duration / zoom : undefined; const zoomedDuration = isDurationValid(duration) ? duration / zoom : undefined;
const { 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, enableSmartCut, setEnableSmartCut, customFfPath, setCustomFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, 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, enableSmartCut, setEnableSmartCut, customFfPath, setCustomFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, enableOverwriteOutput, setEnableOverwriteOutput,
} = useUserSettingsRoot(); } = useUserSettingsRoot();
useEffect(() => { useEffect(() => {
@ -754,8 +754,8 @@ const App = memo(() => {
}, [autoDeleteMergedSegments, autoMerge, segmentsToChaptersOnly]); }, [autoDeleteMergedSegments, autoMerge, segmentsToChaptersOnly]);
const userSettingsContext = useMemo(() => ({ const userSettingsContext = useMemo(() => ({
captureFormat, setCaptureFormat, toggleCaptureFormat, customOutDir, setCustomOutDir, changeOutDir, keyframeCut, setKeyframeCut, toggleKeyframeCut, preserveMovData, setPreserveMovData, togglePreserveMovData, movFastStart, setMovFastStart, toggleMovFastStart, 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, toggleExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, toggleSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, togglePreserveMetadataOnMerge, simpleMode, setSimpleMode, toggleSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, setKeyboardSeekAccFactor, keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed, enableTransferTimestamps, setEnableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, toggleSafeOutputFileName, enableAutoHtml5ify, setEnableAutoHtml5ify, segmentsToChaptersOnly, setSegmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, setEnableSmartCut, customFfPath, setCustomFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, effectiveExportMode, captureFormat, setCaptureFormat, toggleCaptureFormat, customOutDir, setCustomOutDir, changeOutDir, keyframeCut, setKeyframeCut, toggleKeyframeCut, preserveMovData, setPreserveMovData, togglePreserveMovData, movFastStart, setMovFastStart, toggleMovFastStart, 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, toggleExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, toggleSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, togglePreserveMetadataOnMerge, simpleMode, setSimpleMode, toggleSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, setKeyboardSeekAccFactor, keyboardNormalSeekSpeed, setKeyboardNormalSeekSpeed, enableTransferTimestamps, setEnableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, toggleSafeOutputFileName, enableAutoHtml5ify, setEnableAutoHtml5ify, segmentsToChaptersOnly, setSegmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, setEnableSmartCut, customFfPath, setCustomFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, effectiveExportMode, enableOverwriteOutput, setEnableOverwriteOutput,
}), [askBeforeClose, autoDeleteMergedSegments, autoExportExtraStreams, autoLoadTimecode, autoMerge, autoSaveProjectFile, avoidNegativeTs, captureFormat, changeOutDir, customFfPath, customOutDir, effectiveExportMode, enableAskForFileOpenAction, enableAskForImportChapters, enableAutoHtml5ify, enableSmartCut, enableTransferTimestamps, exportConfirmEnabled, ffmpegExperimental, hideNotifications, invertCutSegments, invertTimelineScroll, keyBindings, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyframeCut, language, movFastStart, outFormatLocked, outSegTemplate, playbackVolume, preserveMetadataOnMerge, preserveMovData, resetKeyBindings, safeOutputFileName, segmentsToChapters, segmentsToChaptersOnly, setAskBeforeClose, setAutoDeleteMergedSegments, setAutoExportExtraStreams, setAutoLoadTimecode, setAutoMerge, setAutoSaveProjectFile, setAvoidNegativeTs, setCaptureFormat, setCustomFfPath, setCustomOutDir, setEnableAskForFileOpenAction, setEnableAskForImportChapters, setEnableAutoHtml5ify, setEnableSmartCut, setEnableTransferTimestamps, setExportConfirmEnabled, setFfmpegExperimental, setHideNotifications, setInvertCutSegments, setInvertTimelineScroll, setKeyBindings, setKeyboardNormalSeekSpeed, setKeyboardSeekAccFactor, setKeyframeCut, setLanguage, setMovFastStart, setOutFormatLocked, setOutSegTemplate, setPlaybackVolume, setPreserveMetadataOnMerge, setPreserveMovData, setSafeOutputFileName, setSegmentsToChapters, setSegmentsToChaptersOnly, setSimpleMode, setStoreProjectInWorkingDir, setTimecodeFormat, setWheelSensitivity, simpleMode, storeProjectInWorkingDir, timecodeFormat, toggleCaptureFormat, toggleExportConfirmEnabled, toggleKeyframeCut, toggleMovFastStart, togglePreserveMetadataOnMerge, togglePreserveMovData, toggleSafeOutputFileName, toggleSegmentsToChapters, toggleSimpleMode, wheelSensitivity]); }), [askBeforeClose, autoDeleteMergedSegments, autoExportExtraStreams, autoLoadTimecode, autoMerge, autoSaveProjectFile, avoidNegativeTs, captureFormat, changeOutDir, customFfPath, customOutDir, effectiveExportMode, enableAskForFileOpenAction, enableAskForImportChapters, enableAutoHtml5ify, enableOverwriteOutput, enableSmartCut, enableTransferTimestamps, exportConfirmEnabled, ffmpegExperimental, hideNotifications, invertCutSegments, invertTimelineScroll, keyBindings, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyframeCut, language, movFastStart, outFormatLocked, outSegTemplate, playbackVolume, preserveMetadataOnMerge, preserveMovData, resetKeyBindings, safeOutputFileName, segmentsToChapters, segmentsToChaptersOnly, setAskBeforeClose, setAutoDeleteMergedSegments, setAutoExportExtraStreams, setAutoLoadTimecode, setAutoMerge, setAutoSaveProjectFile, setAvoidNegativeTs, setCaptureFormat, setCustomFfPath, setCustomOutDir, setEnableAskForFileOpenAction, setEnableAskForImportChapters, setEnableAutoHtml5ify, setEnableOverwriteOutput, setEnableSmartCut, setEnableTransferTimestamps, setExportConfirmEnabled, setFfmpegExperimental, setHideNotifications, setInvertCutSegments, setInvertTimelineScroll, setKeyBindings, setKeyboardNormalSeekSpeed, setKeyboardSeekAccFactor, setKeyframeCut, setLanguage, setMovFastStart, setOutFormatLocked, setOutSegTemplate, setPlaybackVolume, setPreserveMetadataOnMerge, setPreserveMovData, setSafeOutputFileName, setSegmentsToChapters, setSegmentsToChaptersOnly, setSimpleMode, setStoreProjectInWorkingDir, setTimecodeFormat, setWheelSensitivity, simpleMode, storeProjectInWorkingDir, timecodeFormat, toggleCaptureFormat, toggleExportConfirmEnabled, toggleKeyframeCut, toggleMovFastStart, togglePreserveMetadataOnMerge, togglePreserveMovData, toggleSafeOutputFileName, toggleSegmentsToChapters, toggleSimpleMode, wheelSensitivity]);
const isCopyingStreamId = useCallback((path, streamId) => ( const isCopyingStreamId = useCallback((path, streamId) => (
!!(copyStreamIdsByFile[path] || {})[streamId] !!(copyStreamIdsByFile[path] || {})[streamId]
@ -1323,6 +1323,7 @@ const App = memo(() => {
chapters: chaptersToAdd, chapters: chaptersToAdd,
detectedFps, detectedFps,
enableSmartCut, enableSmartCut,
enableOverwriteOutput,
}); });
if (willMerge) { if (willMerge) {
@ -1354,7 +1355,7 @@ const App = memo(() => {
if (exportExtraStreams) { if (exportExtraStreams) {
try { try {
await extractStreams({ filePath, customOutDir, streams: nonCopiedExtraStreams }); await extractStreams({ filePath, customOutDir, streams: nonCopiedExtraStreams, enableOverwriteOutput });
msgs.push(i18n.t('Unprocessable streams were exported as separate files.')); msgs.push(i18n.t('Unprocessable streams were exported as separate files.'));
} catch (err) { } catch (err) {
console.error('Extra stream export failed', err); console.error('Extra stream export failed', err);
@ -1363,6 +1364,11 @@ const App = memo(() => {
if (!hideAllNotifications) openDirToast({ dirPath: outputDir, text: msgs.join(' '), timer: 15000 }); if (!hideAllNotifications) openDirToast({ dirPath: outputDir, text: msgs.join(' '), timer: 15000 });
} catch (err) { } catch (err) {
if (err instanceof RefuseOverwriteError) {
showRefuseToOverwrite();
return;
}
console.error('stdout:', err.stdout); console.error('stdout:', err.stdout);
console.error('stderr:', err.stderr); console.error('stderr:', err.stderr);
@ -1380,7 +1386,7 @@ const App = memo(() => {
setWorking(); setWorking();
setCutProgress(); setCutProgress();
} }
}, [numStreamsToCopy, setWorking, segmentsToChaptersOnly, selectedSegmentsOrInverse, outSegTemplateOrDefault, generateOutSegFileNames, segmentsToExport, getOutSegError, cutMultiple, outputDir, customOutDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, preserveMetadataOnMerge, movFastStart, avoidNegativeTs, customTagsByFile, customTagsByStreamId, dispositionByStreamId, detectedFps, enableSmartCut, willMerge, mainFileFormatData, mainStreams, exportExtraStreams, hideAllNotifications, segmentsToChapters, invertCutSegments, autoConcatCutSegments, isCustomFormatSelected, autoDeleteMergedSegments, filePath, nonCopiedExtraStreams, handleCutFailed]); }, [numStreamsToCopy, setWorking, segmentsToChaptersOnly, outSegTemplateOrDefault, generateOutSegFileNames, segmentsToExport, getOutSegError, cutMultiple, outputDir, customOutDir, fileFormat, duration, isRotationSet, effectiveRotation, copyFileStreams, allFilesMeta, keyframeCut, shortestFlag, ffmpegExperimental, preserveMovData, preserveMetadataOnMerge, movFastStart, avoidNegativeTs, customTagsByFile, customTagsByStreamId, dispositionByStreamId, detectedFps, enableSmartCut, enableOverwriteOutput, willMerge, mainFileFormatData, mainStreams, exportExtraStreams, hideAllNotifications, selectedSegmentsOrInverse, segmentsToChapters, invertCutSegments, autoConcatCutSegments, isCustomFormatSelected, autoDeleteMergedSegments, filePath, nonCopiedExtraStreams, handleCutFailed]);
const onExportPress = useCallback(async () => { const onExportPress = useCallback(async () => {
if (!filePath || workingRef.current || segmentsToExport.length < 1) return; if (!filePath || workingRef.current || segmentsToExport.length < 1) return;
@ -1736,15 +1742,19 @@ const App = memo(() => {
try { try {
setWorking(i18n.t('Extracting all streams')); setWorking(i18n.t('Extracting all streams'));
setStreamsSelectorShown(false); setStreamsSelectorShown(false);
await extractStreams({ customOutDir, filePath, streams: mainStreams }); await extractStreams({ customOutDir, filePath, streams: mainStreams, enableOverwriteOutput });
openDirToast({ dirPath: outputDir, text: i18n.t('All streams have been extracted as separate files') }); openDirToast({ dirPath: outputDir, text: i18n.t('All streams have been extracted as separate files') });
} catch (err) { } catch (err) {
if (err instanceof RefuseOverwriteError) {
showRefuseToOverwrite();
return;
}
errorToast(i18n.t('Failed to extract all streams')); errorToast(i18n.t('Failed to extract all streams'));
console.error('Failed to extract all streams', err); console.error('Failed to extract all streams', err);
} finally { } finally {
setWorking(); setWorking();
} }
}, [customOutDir, filePath, mainStreams, outputDir, setWorking]); }, [customOutDir, enableOverwriteOutput, filePath, mainStreams, outputDir, setWorking]);
const detectBlackScenes = useCallback(async () => { const detectBlackScenes = useCallback(async () => {
if (!filePath) return; if (!filePath) return;
@ -2007,15 +2017,19 @@ const App = memo(() => {
try { try {
setWorking(i18n.t('Extracting track')); setWorking(i18n.t('Extracting track'));
// setStreamsSelectorShown(false); // setStreamsSelectorShown(false);
await extractStreams({ customOutDir, filePath, streams: mainStreams.filter((s) => s.index === index) }); await extractStreams({ customOutDir, filePath, streams: mainStreams.filter((s) => s.index === index), enableOverwriteOutput });
openDirToast({ dirPath: outputDir, text: i18n.t('Track has been extracted') }); openDirToast({ dirPath: outputDir, text: i18n.t('Track has been extracted') });
} catch (err) { } catch (err) {
if (err instanceof RefuseOverwriteError) {
showRefuseToOverwrite();
return;
}
errorToast(i18n.t('Failed to extract track')); errorToast(i18n.t('Failed to extract track'));
console.error('Failed to extract track', err); console.error('Failed to extract track', err);
} finally { } finally {
setWorking(); setWorking();
} }
}, [customOutDir, filePath, mainStreams, outputDir, setWorking]); }, [customOutDir, enableOverwriteOutput, filePath, mainStreams, outputDir, setWorking]);
const addStreamSourceFile = useCallback(async (path) => { const addStreamSourceFile = useCallback(async (path) => {
if (allFilesMeta[path]) return; if (allFilesMeta[path]) return;

Wyświetl plik

@ -47,7 +47,7 @@ const Settings = memo(({
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { customOutDir, changeOutDir, keyframeCut, toggleKeyframeCut, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, autoSaveProjectFile, setAutoSaveProjectFile, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, enableTransferTimestamps, setEnableTransferTimestamps, enableAutoHtml5ify, setEnableAutoHtml5ify, customFfPath, setCustomFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir } = useUserSettings(); const { customOutDir, changeOutDir, keyframeCut, toggleKeyframeCut, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, autoSaveProjectFile, setAutoSaveProjectFile, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, enableTransferTimestamps, setEnableTransferTimestamps, enableAutoHtml5ify, setEnableAutoHtml5ify, customFfPath, setCustomFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, enableOverwriteOutput, setEnableOverwriteOutput } = useUserSettings();
const onLangChange = useCallback((e) => { const onLangChange = useCallback((e) => {
const { value } = e.target; const { value } = e.target;
@ -271,6 +271,17 @@ const Settings = memo(({
</Table.TextCell> </Table.TextCell>
</Row> </Row>
<Row>
<KeyCell>{t('Overwrite files when exporting, if a file with the same name as the output file name exists?')}</KeyCell>
<Table.TextCell>
<Checkbox
label={t('Overwrite existing files')}
checked={enableOverwriteOutput}
onChange={e => setEnableOverwriteOutput(e.target.checked)}
/>
</Table.TextCell>
</Row>
<Row> <Row>
<KeyCell>{t('Auto load timecode from file as an offset in the timeline?')}</KeyCell> <KeyCell>{t('Auto load timecode from file as an offset in the timeline?')}</KeyCell>
<Table.TextCell> <Table.TextCell>

Wyświetl plik

@ -169,6 +169,13 @@ export async function showDiskFull() {
}); });
} }
export async function showRefuseToOverwrite() {
await Swal.fire({
icon: 'warning',
text: i18n.t('Output file already exists, refusing to overwrite. You can turn on overwriting in settings.'),
});
}
export async function askForImportChapters() { export async function askForImportChapters() {
const { value } = await Swal.fire({ const { value } = await Swal.fire({
icon: 'question', icon: 'question',

Wyświetl plik

@ -1,5 +1,4 @@
import pMap from 'p-map'; import pMap from 'p-map';
import flatMap from 'lodash/flatMap';
import sortBy from 'lodash/sortBy'; import sortBy from 'lodash/sortBy';
import moment from 'moment'; import moment from 'moment';
import i18n from 'i18next'; import i18n from 'i18next';
@ -13,9 +12,13 @@ const { join } = window.require('path');
const FileType = window.require('file-type'); const FileType = window.require('file-type');
const readline = window.require('readline'); const readline = window.require('readline');
const isDev = window.require('electron-is-dev'); const isDev = window.require('electron-is-dev');
const { pathExists } = window.require('fs-extra');
let customFfPath; let customFfPath;
export class RefuseOverwriteError extends Error {}
// Note that this does not work on MAS because of sandbox restrictions // Note that this does not work on MAS because of sandbox restrictions
export function setCustomFfPath(path) { export function setCustomFfPath(path) {
customFfPath = path; customFfPath = path;
@ -325,14 +328,21 @@ function getPreferredCodecFormat({ codec_name: codec, codec_type: type }) {
return undefined; return undefined;
} }
async function extractNonAttachmentStreams({ customOutDir, filePath, streams }) { async function extractNonAttachmentStreams({ customOutDir, filePath, streams, enableOverwriteOutput }) {
if (streams.length === 0) return; if (streams.length === 0) return;
console.log('Extracting', streams.length, 'normal streams'); console.log('Extracting', streams.length, 'normal streams');
const streamArgs = flatMap(streams, ({ index, codec, type, format: { format, ext } }) => [ let streamArgs = [];
'-map', `0:${index}`, '-c', 'copy', '-f', format, '-y', getOutPath({ customOutDir, filePath, nameSuffix: `stream-${index}-${type}-${codec}.${ext}` }), await pMap(streams, async ({ index, codec, type, format: { format, ext } }) => {
]); const outPath = getOutPath({ customOutDir, filePath, nameSuffix: `stream-${index}-${type}-${codec}.${ext}` });
if (!enableOverwriteOutput && await pathExists(outPath)) throw new RefuseOverwriteError();
streamArgs = [
...streamArgs,
'-map', `0:${index}`, '-c', 'copy', '-f', format, '-y', outPath,
];
}, { concurrency: 1 });
const ffmpegArgs = [ const ffmpegArgs = [
'-hide_banner', '-hide_banner',
@ -345,17 +355,22 @@ async function extractNonAttachmentStreams({ customOutDir, filePath, streams })
console.log(stdout); console.log(stdout);
} }
async function extractAttachmentStreams({ customOutDir, filePath, streams }) { async function extractAttachmentStreams({ customOutDir, filePath, streams, enableOverwriteOutput }) {
if (streams.length === 0) return; if (streams.length === 0) return;
console.log('Extracting', streams.length, 'attachment streams'); console.log('Extracting', streams.length, 'attachment streams');
const streamArgs = flatMap(streams, ({ index, codec_name: codec, codec_type: type }) => { let streamArgs = [];
await pMap(streams, async ({ index, codec_name: codec, codec_type: type }) => {
const ext = codec || 'bin'; const ext = codec || 'bin';
return [ const outPath = getOutPath({ customOutDir, filePath, nameSuffix: `stream-${index}-${type}-${codec}.${ext}` });
`-dump_attachment:${index}`, getOutPath({ customOutDir, filePath, nameSuffix: `stream-${index}-${type}-${codec}.${ext}` }), if (!enableOverwriteOutput && await pathExists(outPath)) throw new RefuseOverwriteError();
streamArgs = [
...streamArgs,
`-dump_attachment:${index}`, outPath,
]; ];
}); }, { concurrency: 1 });
const ffmpegArgs = [ const ffmpegArgs = [
'-y', '-y',
@ -377,7 +392,7 @@ async function extractAttachmentStreams({ customOutDir, filePath, streams }) {
} }
// https://stackoverflow.com/questions/32922226/extract-every-audio-and-subtitles-from-a-video-with-ffmpeg // https://stackoverflow.com/questions/32922226/extract-every-audio-and-subtitles-from-a-video-with-ffmpeg
export async function extractStreams({ filePath, customOutDir, streams }) { export async function extractStreams({ filePath, customOutDir, streams, enableOverwriteOutput }) {
const attachmentStreams = streams.filter((s) => s.codec_type === 'attachment'); const attachmentStreams = streams.filter((s) => s.codec_type === 'attachment');
const nonAttachmentStreams = streams.filter((s) => s.codec_type !== 'attachment'); const nonAttachmentStreams = streams.filter((s) => s.codec_type !== 'attachment');
@ -394,8 +409,8 @@ export async function extractStreams({ filePath, customOutDir, streams }) {
// TODO progress // TODO progress
// Attachment streams are handled differently from normal streams // Attachment streams are handled differently from normal streams
await extractNonAttachmentStreams({ customOutDir, filePath, streams: outStreams }); await extractNonAttachmentStreams({ customOutDir, filePath, streams: outStreams, enableOverwriteOutput });
await extractAttachmentStreams({ customOutDir, filePath, streams: attachmentStreams }); await extractAttachmentStreams({ customOutDir, filePath, streams: attachmentStreams, enableOverwriteOutput });
} }
async function renderThumbnail(filePath, timestamp) { async function renderThumbnail(filePath, timestamp) {

Wyświetl plik

@ -4,7 +4,7 @@ import sum from 'lodash/sum';
import pMap from 'p-map'; import pMap from 'p-map';
import { getOutPath, transferTimestamps, getOutFileExtension, getOutDir, deleteDispositionValue, getHtml5ifiedPath } from '../util'; import { getOutPath, transferTimestamps, getOutFileExtension, getOutDir, deleteDispositionValue, getHtml5ifiedPath } from '../util';
import { isCuttingStart, isCuttingEnd, handleProgress, getFfCommandLine, getFfmpegPath, getDuration, runFfmpeg, createChaptersFromSegments, readFileMeta, cutEncodeSmartPart, getExperimentalArgs, html5ify as ffmpegHtml5ify, getVideoTimescaleArgs } from '../ffmpeg'; import { isCuttingStart, isCuttingEnd, handleProgress, getFfCommandLine, getFfmpegPath, getDuration, runFfmpeg, createChaptersFromSegments, readFileMeta, cutEncodeSmartPart, getExperimentalArgs, html5ify as ffmpegHtml5ify, getVideoTimescaleArgs, RefuseOverwriteError } from '../ffmpeg';
import { getMapStreamsArgs, getStreamIdsToCopy } from '../util/streams'; import { getMapStreamsArgs, getStreamIdsToCopy } from '../util/streams';
import { getSmartCutParams } from '../smartcut'; import { getSmartCutParams } from '../smartcut';
@ -326,6 +326,7 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
onProgress: onTotalProgress, keyframeCut, copyFileStreams, allFilesMeta, outFormat, onProgress: onTotalProgress, keyframeCut, copyFileStreams, allFilesMeta, outFormat,
appendFfmpegCommandLog, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, avoidNegativeTs, appendFfmpegCommandLog, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, avoidNegativeTs,
customTagsByFile, customTagsByStreamId, dispositionByStreamId, chapters, preserveMetadataOnMerge, enableSmartCut, customTagsByFile, customTagsByStreamId, dispositionByStreamId, chapters, preserveMetadataOnMerge, enableSmartCut,
enableOverwriteOutput,
}) => { }) => {
console.log('customTagsByFile', customTagsByFile); console.log('customTagsByFile', customTagsByFile);
console.log('customTagsByStreamId', customTagsByStreamId); console.log('customTagsByStreamId', customTagsByStreamId);
@ -338,6 +339,10 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
const chaptersPath = await writeChaptersFfmetadata(outputDir, chapters); const chaptersPath = await writeChaptersFfmetadata(outputDir, chapters);
async function checkOverwrite(path) {
if (!enableOverwriteOutput && await fs.pathExists(path)) throw new RefuseOverwriteError();
}
// This function will either call cutSingle (if no smart cut enabled) // This function will either call cutSingle (if no smart cut enabled)
// or if enabled, will first cut&encode the part before the next keyframe, trying to match the input file's codec params // or if enabled, will first cut&encode the part before the next keyframe, trying to match the input file's codec params
@ -348,6 +353,7 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
if (!enableSmartCut) { if (!enableSmartCut) {
// old fashioned way // old fashioned way
const outPath = getSegmentOutPath(); const outPath = getSegmentOutPath();
await checkOverwrite(outPath);
await cutSingle({ await cutSingle({
cutFrom: desiredCutFrom, cutTo, chaptersPath, outPath, copyFileStreams, keyframeCut, avoidNegativeTs, videoDuration, rotation, allFilesMeta, outFormat, appendFfmpegCommandLog, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, customTagsByFile, customTagsByStreamId, dispositionByStreamId, onProgress: (progress) => onSingleProgress(i, progress), cutFrom: desiredCutFrom, cutTo, chaptersPath, outPath, copyFileStreams, keyframeCut, avoidNegativeTs, videoDuration, rotation, allFilesMeta, outFormat, appendFfmpegCommandLog, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, customTagsByFile, customTagsByStreamId, dispositionByStreamId, onProgress: (progress) => onSingleProgress(i, progress),
}); });
@ -378,6 +384,8 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
? getOutPath({ customOutDir, filePath, nameSuffix: `smartcut-segment-copy-${i}${ext}` }) ? getOutPath({ customOutDir, filePath, nameSuffix: `smartcut-segment-copy-${i}${ext}` })
: getSegmentOutPath(); : getSegmentOutPath();
if (!needsSmartCut) await checkOverwrite(smartCutMainPartOutPath);
const smartCutEncodedPartOutPath = getOutPath({ customOutDir, filePath, nameSuffix: `smartcut-segment-encode-${i}${ext}` }); const smartCutEncodedPartOutPath = getOutPath({ customOutDir, filePath, nameSuffix: `smartcut-segment-encode-${i}${ext}` });
const smartCutSegmentsToConcat = [smartCutEncodedPartOutPath, smartCutMainPartOutPath]; const smartCutSegmentsToConcat = [smartCutEncodedPartOutPath, smartCutMainPartOutPath];
@ -401,6 +409,7 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
const { streams: streamsAfterCut } = await readFileMeta(smartCutMainPartOutPath); const { streams: streamsAfterCut } = await readFileMeta(smartCutMainPartOutPath);
const outPath = getSegmentOutPath(); const outPath = getSegmentOutPath();
await checkOverwrite(outPath);
await concatFiles({ paths: smartCutSegmentsToConcat, outDir: outputDir, outPath, metadataFromPath: smartCutMainPartOutPath, outFormat, includeAllStreams: true, streams: streamsAfterCut, ffmpegExperimental, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge, videoTimebase, appendFfmpegCommandLog, onProgress: onConcatProgress }); await concatFiles({ paths: smartCutSegmentsToConcat, outDir: outputDir, outPath, metadataFromPath: smartCutMainPartOutPath, outFormat, includeAllStreams: true, streams: streamsAfterCut, ffmpegExperimental, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge, videoTimebase, appendFfmpegCommandLog, onProgress: onConcatProgress });
return outPath; return outPath;

Wyświetl plik

@ -113,6 +113,8 @@ export default () => {
useEffect(() => safeSetConfig('customFfPath', customFfPath), [customFfPath]); useEffect(() => safeSetConfig('customFfPath', customFfPath), [customFfPath]);
const [storeProjectInWorkingDir, setStoreProjectInWorkingDir] = useState(safeGetConfigInitial('storeProjectInWorkingDir')); const [storeProjectInWorkingDir, setStoreProjectInWorkingDir] = useState(safeGetConfigInitial('storeProjectInWorkingDir'));
useEffect(() => safeSetConfig('storeProjectInWorkingDir', storeProjectInWorkingDir), [storeProjectInWorkingDir]); useEffect(() => safeSetConfig('storeProjectInWorkingDir', storeProjectInWorkingDir), [storeProjectInWorkingDir]);
const [enableOverwriteOutput, setEnableOverwriteOutput] = useState(safeGetConfigInitial('enableOverwriteOutput'));
useEffect(() => safeSetConfig('enableOverwriteOutput', enableOverwriteOutput), [enableOverwriteOutput]);
const resetKeyBindings = useCallback(() => { const resetKeyBindings = useCallback(() => {
configStore.reset('keyBindings'); configStore.reset('keyBindings');
@ -205,5 +207,7 @@ export default () => {
setCustomFfPath, setCustomFfPath,
storeProjectInWorkingDir, storeProjectInWorkingDir,
setStoreProjectInWorkingDir, setStoreProjectInWorkingDir,
enableOverwriteOutput,
setEnableOverwriteOutput,
}; };
}; };