kopia lustrzana https://github.com/mifi/lossless-cut
rodzic
9a2e21a178
commit
9161278e54
|
@ -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
|
||||||
|
|
36
src/App.jsx
36
src/App.jsx
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Ładowanie…
Reference in New Issue