mirror of
https://github.com/mifi/lossless-cut.git
synced 2024-11-22 10:22:31 +01:00
parent
9a2e21a178
commit
9161278e54
@ -110,6 +110,7 @@ const defaults = {
|
||||
keyBindings: defaultKeyBindings,
|
||||
customFfPath: undefined,
|
||||
storeProjectInWorkingDir: true,
|
||||
enableOverwriteOutput: true,
|
||||
};
|
||||
|
||||
// For portable app: https://github.com/mifi/lossless-cut/issues/645
|
||||
|
36
src/App.jsx
36
src/App.jsx
@ -56,7 +56,7 @@ import {
|
||||
extractStreams, runStartupCheck, setCustomFfPath as ffmpegSetCustomFfPath,
|
||||
isIphoneHevc, tryMapChaptersToEdl, blackDetect,
|
||||
getDuration, getTimecodeFromStreams, createChaptersFromSegments, extractSubtitleTrack,
|
||||
getFfmpegPath,
|
||||
getFfmpegPath, RefuseOverwriteError,
|
||||
} from './ffmpeg';
|
||||
import { shouldCopyStreamByDefault, getAudioStreams, getRealVideoStreams, defaultProcessedCodecTypes, isAudioDefinitelyNotSupported, doesPlayerSupportFile } from './util/streams';
|
||||
import { exportEdlFile, readEdlFile, saveLlcProject, loadLlcProject, askForEdlImport } from './edlStore';
|
||||
@ -70,7 +70,7 @@ import {
|
||||
} from './util';
|
||||
import { formatDuration } from './util/duration';
|
||||
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 { fallbackLng } from './i18n';
|
||||
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 {
|
||||
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();
|
||||
|
||||
useEffect(() => {
|
||||
@ -754,8 +754,8 @@ const App = memo(() => {
|
||||
}, [autoDeleteMergedSegments, autoMerge, segmentsToChaptersOnly]);
|
||||
|
||||
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,
|
||||
}), [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]);
|
||||
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, 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) => (
|
||||
!!(copyStreamIdsByFile[path] || {})[streamId]
|
||||
@ -1323,6 +1323,7 @@ const App = memo(() => {
|
||||
chapters: chaptersToAdd,
|
||||
detectedFps,
|
||||
enableSmartCut,
|
||||
enableOverwriteOutput,
|
||||
});
|
||||
|
||||
if (willMerge) {
|
||||
@ -1354,7 +1355,7 @@ const App = memo(() => {
|
||||
|
||||
if (exportExtraStreams) {
|
||||
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.'));
|
||||
} catch (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 });
|
||||
} catch (err) {
|
||||
if (err instanceof RefuseOverwriteError) {
|
||||
showRefuseToOverwrite();
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('stdout:', err.stdout);
|
||||
console.error('stderr:', err.stderr);
|
||||
|
||||
@ -1380,7 +1386,7 @@ const App = memo(() => {
|
||||
setWorking();
|
||||
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 () => {
|
||||
if (!filePath || workingRef.current || segmentsToExport.length < 1) return;
|
||||
@ -1736,15 +1742,19 @@ const App = memo(() => {
|
||||
try {
|
||||
setWorking(i18n.t('Extracting all streams'));
|
||||
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') });
|
||||
} catch (err) {
|
||||
if (err instanceof RefuseOverwriteError) {
|
||||
showRefuseToOverwrite();
|
||||
return;
|
||||
}
|
||||
errorToast(i18n.t('Failed to extract all streams'));
|
||||
console.error('Failed to extract all streams', err);
|
||||
} finally {
|
||||
setWorking();
|
||||
}
|
||||
}, [customOutDir, filePath, mainStreams, outputDir, setWorking]);
|
||||
}, [customOutDir, enableOverwriteOutput, filePath, mainStreams, outputDir, setWorking]);
|
||||
|
||||
const detectBlackScenes = useCallback(async () => {
|
||||
if (!filePath) return;
|
||||
@ -2007,15 +2017,19 @@ const App = memo(() => {
|
||||
try {
|
||||
setWorking(i18n.t('Extracting track'));
|
||||
// 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') });
|
||||
} catch (err) {
|
||||
if (err instanceof RefuseOverwriteError) {
|
||||
showRefuseToOverwrite();
|
||||
return;
|
||||
}
|
||||
errorToast(i18n.t('Failed to extract track'));
|
||||
console.error('Failed to extract track', err);
|
||||
} finally {
|
||||
setWorking();
|
||||
}
|
||||
}, [customOutDir, filePath, mainStreams, outputDir, setWorking]);
|
||||
}, [customOutDir, enableOverwriteOutput, filePath, mainStreams, outputDir, setWorking]);
|
||||
|
||||
const addStreamSourceFile = useCallback(async (path) => {
|
||||
if (allFilesMeta[path]) return;
|
||||
|
@ -47,7 +47,7 @@ const Settings = memo(({
|
||||
}) => {
|
||||
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 { value } = e.target;
|
||||
@ -271,6 +271,17 @@ const Settings = memo(({
|
||||
</Table.TextCell>
|
||||
</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>
|
||||
<KeyCell>{t('Auto load timecode from file as an offset in the timeline?')}</KeyCell>
|
||||
<Table.TextCell>
|
||||
|
@ -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() {
|
||||
const { value } = await Swal.fire({
|
||||
icon: 'question',
|
||||
|
@ -1,5 +1,4 @@
|
||||
import pMap from 'p-map';
|
||||
import flatMap from 'lodash/flatMap';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
import moment from 'moment';
|
||||
import i18n from 'i18next';
|
||||
@ -13,9 +12,13 @@ const { join } = window.require('path');
|
||||
const FileType = window.require('file-type');
|
||||
const readline = window.require('readline');
|
||||
const isDev = window.require('electron-is-dev');
|
||||
const { pathExists } = window.require('fs-extra');
|
||||
|
||||
let customFfPath;
|
||||
|
||||
|
||||
export class RefuseOverwriteError extends Error {}
|
||||
|
||||
// Note that this does not work on MAS because of sandbox restrictions
|
||||
export function setCustomFfPath(path) {
|
||||
customFfPath = path;
|
||||
@ -325,14 +328,21 @@ function getPreferredCodecFormat({ codec_name: codec, codec_type: type }) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function extractNonAttachmentStreams({ customOutDir, filePath, streams }) {
|
||||
async function extractNonAttachmentStreams({ customOutDir, filePath, streams, enableOverwriteOutput }) {
|
||||
if (streams.length === 0) return;
|
||||
|
||||
console.log('Extracting', streams.length, 'normal streams');
|
||||
|
||||
const streamArgs = flatMap(streams, ({ index, codec, type, format: { format, ext } }) => [
|
||||
'-map', `0:${index}`, '-c', 'copy', '-f', format, '-y', getOutPath({ customOutDir, filePath, nameSuffix: `stream-${index}-${type}-${codec}.${ext}` }),
|
||||
]);
|
||||
let streamArgs = [];
|
||||
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 = [
|
||||
'-hide_banner',
|
||||
@ -345,17 +355,22 @@ async function extractNonAttachmentStreams({ customOutDir, filePath, streams })
|
||||
console.log(stdout);
|
||||
}
|
||||
|
||||
async function extractAttachmentStreams({ customOutDir, filePath, streams }) {
|
||||
async function extractAttachmentStreams({ customOutDir, filePath, streams, enableOverwriteOutput }) {
|
||||
if (streams.length === 0) return;
|
||||
|
||||
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';
|
||||
return [
|
||||
`-dump_attachment:${index}`, getOutPath({ customOutDir, filePath, nameSuffix: `stream-${index}-${type}-${codec}.${ext}` }),
|
||||
const outPath = 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 = [
|
||||
'-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
|
||||
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 nonAttachmentStreams = streams.filter((s) => s.codec_type !== 'attachment');
|
||||
|
||||
@ -394,8 +409,8 @@ export async function extractStreams({ filePath, customOutDir, streams }) {
|
||||
// TODO progress
|
||||
|
||||
// Attachment streams are handled differently from normal streams
|
||||
await extractNonAttachmentStreams({ customOutDir, filePath, streams: outStreams });
|
||||
await extractAttachmentStreams({ customOutDir, filePath, streams: attachmentStreams });
|
||||
await extractNonAttachmentStreams({ customOutDir, filePath, streams: outStreams, enableOverwriteOutput });
|
||||
await extractAttachmentStreams({ customOutDir, filePath, streams: attachmentStreams, enableOverwriteOutput });
|
||||
}
|
||||
|
||||
async function renderThumbnail(filePath, timestamp) {
|
||||
|
@ -4,7 +4,7 @@ import sum from 'lodash/sum';
|
||||
import pMap from 'p-map';
|
||||
|
||||
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 { getSmartCutParams } from '../smartcut';
|
||||
|
||||
@ -326,6 +326,7 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
|
||||
onProgress: onTotalProgress, keyframeCut, copyFileStreams, allFilesMeta, outFormat,
|
||||
appendFfmpegCommandLog, shortestFlag, ffmpegExperimental, preserveMovData, movFastStart, avoidNegativeTs,
|
||||
customTagsByFile, customTagsByStreamId, dispositionByStreamId, chapters, preserveMetadataOnMerge, enableSmartCut,
|
||||
enableOverwriteOutput,
|
||||
}) => {
|
||||
console.log('customTagsByFile', customTagsByFile);
|
||||
console.log('customTagsByStreamId', customTagsByStreamId);
|
||||
@ -338,6 +339,10 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
|
||||
|
||||
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)
|
||||
// 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) {
|
||||
// old fashioned way
|
||||
const outPath = getSegmentOutPath();
|
||||
await checkOverwrite(outPath);
|
||||
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),
|
||||
});
|
||||
@ -378,6 +384,8 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
|
||||
? getOutPath({ customOutDir, filePath, nameSuffix: `smartcut-segment-copy-${i}${ext}` })
|
||||
: getSegmentOutPath();
|
||||
|
||||
if (!needsSmartCut) await checkOverwrite(smartCutMainPartOutPath);
|
||||
|
||||
const smartCutEncodedPartOutPath = getOutPath({ customOutDir, filePath, nameSuffix: `smartcut-segment-encode-${i}${ext}` });
|
||||
|
||||
const smartCutSegmentsToConcat = [smartCutEncodedPartOutPath, smartCutMainPartOutPath];
|
||||
@ -401,6 +409,7 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) {
|
||||
const { streams: streamsAfterCut } = await readFileMeta(smartCutMainPartOutPath);
|
||||
|
||||
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 });
|
||||
return outPath;
|
||||
|
@ -113,6 +113,8 @@ export default () => {
|
||||
useEffect(() => safeSetConfig('customFfPath', customFfPath), [customFfPath]);
|
||||
const [storeProjectInWorkingDir, setStoreProjectInWorkingDir] = useState(safeGetConfigInitial('storeProjectInWorkingDir'));
|
||||
useEffect(() => safeSetConfig('storeProjectInWorkingDir', storeProjectInWorkingDir), [storeProjectInWorkingDir]);
|
||||
const [enableOverwriteOutput, setEnableOverwriteOutput] = useState(safeGetConfigInitial('enableOverwriteOutput'));
|
||||
useEffect(() => safeSetConfig('enableOverwriteOutput', enableOverwriteOutput), [enableOverwriteOutput]);
|
||||
|
||||
const resetKeyBindings = useCallback(() => {
|
||||
configStore.reset('keyBindings');
|
||||
@ -205,5 +207,7 @@ export default () => {
|
||||
setCustomFfPath,
|
||||
storeProjectInWorkingDir,
|
||||
setStoreProjectInWorkingDir,
|
||||
enableOverwriteOutput,
|
||||
setEnableOverwriteOutput,
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user