allow clearing working directory also on MAS build

pull/1496/head
Mikael Finstad 3 years ago
parent 79e0c500d8
commit c2f775c58d
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -180,7 +180,7 @@ const App = memo(() => {
const allUserSettings = useUserSettingsRoot(); const allUserSettings = useUserSettingsRoot();
const { const {
captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, enableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc, cleanupChoices, setCleanupChoices, captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, enableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc, cleanupChoices, setCleanupChoices,
} = allUserSettings; } = allUserSettings;
useEffect(() => { useEffect(() => {
@ -374,15 +374,6 @@ const App = memo(() => {
const outputDir = getOutDir(customOutDir, filePath); const outputDir = getOutDir(customOutDir, filePath);
const changeOutDir = useCallback(async () => {
const newOutDir = await askForOutDir(outputDir);
if (newOutDir) setCustomOutDir(newOutDir);
}, [outputDir, setCustomOutDir]);
const clearOutDir = useCallback(() => {
setCustomOutDir();
}, [setCustomOutDir]);
const usingPreviewFile = !!previewFilePath; const usingPreviewFile = !!previewFilePath;
const effectiveFilePath = previewFilePath || filePath; const effectiveFilePath = previewFilePath || filePath;
const fileUri = effectiveFilePath ? filePathToUrl(effectiveFilePath) : ''; const fileUri = effectiveFilePath ? filePathToUrl(effectiveFilePath) : '';
@ -393,7 +384,8 @@ const App = memo(() => {
const getEdlFilePath = useCallback((fp, cod) => getSuffixedOutPath({ customOutDir: cod, filePath: fp, nameSuffix: projectSuffix }), []); const getEdlFilePath = useCallback((fp, cod) => getSuffixedOutPath({ customOutDir: cod, filePath: fp, nameSuffix: projectSuffix }), []);
// Old versions of LosslessCut used CSV files and stored them always in customOutDir: // Old versions of LosslessCut used CSV files and stored them always in customOutDir:
const getEdlFilePathOld = useCallback((fp, cod) => getSuffixedOutPath({ customOutDir: cod, filePath: fp, nameSuffix: oldProjectSuffix }), []); const getEdlFilePathOld = useCallback((fp, cod) => getSuffixedOutPath({ customOutDir: cod, filePath: fp, nameSuffix: oldProjectSuffix }), []);
const projectFileSavePath = useMemo(() => getEdlFilePath(filePath, storeProjectInWorkingDir ? customOutDir : undefined), [getEdlFilePath, filePath, storeProjectInWorkingDir, customOutDir]); const getProjectFileSavePath = useCallback((storeProjectInWorkingDirIn) => getEdlFilePath(filePath, storeProjectInWorkingDirIn ? customOutDir : undefined), [getEdlFilePath, filePath, customOutDir]);
const projectFileSavePath = useMemo(() => getProjectFileSavePath(storeProjectInWorkingDir), [getProjectFileSavePath, storeProjectInWorkingDir]);
const currentSaveOperation = useMemo(() => { const currentSaveOperation = useMemo(() => {
if (!projectFileSavePath) return undefined; if (!projectFileSavePath) return undefined;
@ -455,7 +447,7 @@ const App = memo(() => {
if (!supportsRotation && !hideAllNotifications) toast.fire({ text: i18n.t('Lossless rotation might not work with this file format. You may try changing to MP4') }); if (!supportsRotation && !hideAllNotifications) toast.fire({ text: i18n.t('Lossless rotation might not work with this file format. You may try changing to MP4') });
}, [hideAllNotifications, fileFormat]); }, [hideAllNotifications, fileFormat]);
const { ensureWritableOutDir, ensureAccessToSourceDir } = useDirectoryAccess({ customOutDir, setCustomOutDir }); const { ensureWritableOutDir, ensureAccessToSourceDir } = useDirectoryAccess({ setCustomOutDir });
const toggleCaptureFormat = useCallback(() => setCaptureFormat(f => (f === 'png' ? 'jpeg' : 'png')), [setCaptureFormat]); const toggleCaptureFormat = useCallback(() => setCaptureFormat(f => (f === 'png' ? 'jpeg' : 'png')), [setCaptureFormat]);
@ -486,6 +478,35 @@ const App = memo(() => {
return 'separate'; return 'separate';
}, [autoDeleteMergedSegments, autoMerge, segmentsToChaptersOnly]); }, [autoDeleteMergedSegments, autoMerge, segmentsToChaptersOnly]);
const changeOutDir = useCallback(async () => {
const newOutDir = await askForOutDir(outputDir);
if (newOutDir) setCustomOutDir(newOutDir);
}, [outputDir, setCustomOutDir]);
const clearOutDir = useCallback(async () => {
try {
await ensureWritableOutDir({ inputPath: filePath, outDir: undefined });
setCustomOutDir();
} catch (err) {
if (err instanceof DirectoryAccessDeclinedError) return;
throw err;
}
}, [ensureWritableOutDir, filePath, setCustomOutDir]);
const toggleStoreProjectInWorkingDir = useCallback(async () => {
const newValue = !storeProjectInWorkingDir;
const path = getProjectFileSavePath(newValue);
if (path) { // path will be falsy if no file loaded
try {
await ensureAccessToSourceDir(path);
} catch (err) {
if (err instanceof DirectoryAccessDeclinedError) return;
console.error(err);
}
}
setStoreProjectInWorkingDir(newValue);
}, [ensureAccessToSourceDir, getProjectFileSavePath, setStoreProjectInWorkingDir, storeProjectInWorkingDir]);
const userSettingsContext = useMemo(() => ({ const userSettingsContext = useMemo(() => ({
...allUserSettings, toggleCaptureFormat, changeOutDir, toggleKeyframeCut, togglePreserveMovData, toggleMovFastStart, toggleExportConfirmEnabled, toggleSegmentsToChapters, togglePreserveMetadataOnMerge, toggleSimpleMode, toggleSafeOutputFileName, effectiveExportMode, ...allUserSettings, toggleCaptureFormat, changeOutDir, toggleKeyframeCut, togglePreserveMovData, toggleMovFastStart, toggleExportConfirmEnabled, toggleSegmentsToChapters, togglePreserveMetadataOnMerge, toggleSimpleMode, toggleSafeOutputFileName, effectiveExportMode,
}), [allUserSettings, changeOutDir, effectiveExportMode, toggleCaptureFormat, toggleExportConfirmEnabled, toggleKeyframeCut, toggleMovFastStart, togglePreserveMetadataOnMerge, togglePreserveMovData, toggleSafeOutputFileName, toggleSegmentsToChapters, toggleSimpleMode]); }), [allUserSettings, changeOutDir, effectiveExportMode, toggleCaptureFormat, toggleExportConfirmEnabled, toggleKeyframeCut, toggleMovFastStart, togglePreserveMetadataOnMerge, togglePreserveMovData, toggleSafeOutputFileName, toggleSegmentsToChapters, toggleSimpleMode]);
@ -734,7 +755,7 @@ const App = memo(() => {
for (const path of filePaths) { for (const path of filePaths) {
try { try {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
const newCustomOutDir = await ensureWritableOutDir(path); const newCustomOutDir = await ensureWritableOutDir({ inputPath: path, outDir: customOutDir });
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await html5ify({ customOutDir: newCustomOutDir, filePath: path, speed, hasAudio: true, hasVideo: true, onProgress: setTotalProgress }); await html5ify({ customOutDir: newCustomOutDir, filePath: path, speed, hasAudio: true, hasVideo: true, onProgress: setTotalProgress });
@ -757,7 +778,7 @@ const App = memo(() => {
setWorking(); setWorking();
setCutProgress(); setCutProgress();
} }
}, [batchFiles, ensureWritableOutDir, html5ify, setWorking]); }, [batchFiles, customOutDir, ensureWritableOutDir, html5ify, setWorking]);
const getConvertToSupportedFormat = useCallback((fallback) => rememberConvertToSupportedFormat || fallback, [rememberConvertToSupportedFormat]); const getConvertToSupportedFormat = useCallback((fallback) => rememberConvertToSupportedFormat || fallback, [rememberConvertToSupportedFormat]);
@ -920,7 +941,7 @@ const App = memo(() => {
const firstPath = paths[0]; const firstPath = paths[0];
if (!firstPath) return; if (!firstPath) return;
const newCustomOutDir = await ensureWritableOutDir(firstPath); const newCustomOutDir = await ensureWritableOutDir({ inputPath: firstPath, outDir: customOutDir });
const outDir = getOutDir(newCustomOutDir, firstPath); const outDir = getOutDir(newCustomOutDir, firstPath);
@ -974,7 +995,7 @@ const App = memo(() => {
setWorking(); setWorking();
setCutProgress(); setCutProgress();
} }
}, [setWorking, ensureWritableOutDir, segmentsToChapters, concatFiles, ffmpegExperimental, preserveMovData, movFastStart, preserveMetadataOnMerge, closeBatch, hideAllNotifications, handleConcatFailed]); }, [setWorking, ensureWritableOutDir, customOutDir, segmentsToChapters, concatFiles, ffmpegExperimental, preserveMovData, movFastStart, preserveMetadataOnMerge, closeBatch, hideAllNotifications, handleConcatFailed]);
const cleanupFiles = useCallback(async (cleanupChoices2) => { const cleanupFiles = useCallback(async (cleanupChoices2) => {
// Store paths before we reset state // Store paths before we reset state
@ -1370,7 +1391,7 @@ const App = memo(() => {
const hevcPlaybackSupported = enableNativeHevc && await hevcPlaybackSupportedPromise; const hevcPlaybackSupported = enableNativeHevc && await hevcPlaybackSupportedPromise;
// need to ensure we have access to write to working directory // need to ensure we have access to write to working directory
const cod = await ensureWritableOutDir(fp); const cod = await ensureWritableOutDir({ inputPath: fp, outDir: customOutDir });
// if storeProjectInSourceDir is true, we will be writing project file to input path's dir, so ensure that one too // if storeProjectInSourceDir is true, we will be writing project file to input path's dir, so ensure that one too
if (storeProjectInSourceDir) await ensureAccessToSourceDir(fp); if (storeProjectInSourceDir) await ensureAccessToSourceDir(fp);
@ -1436,7 +1457,7 @@ const App = memo(() => {
resetState(); resetState();
throw err; throw err;
} }
}, [setWorking, loadEdlFile, getEdlFilePath, getEdlFilePathOld, enableAskForImportChapters, loadCutSegments, autoLoadTimecode, enableNativeHevc, ensureWritableOutDir, storeProjectInWorkingDir, ensureAccessToSourceDir, resetState, setCopyStreamIdsForPath, setFileFormat, outFormatLocked, setDetectedFileFormat, html5ifyAndLoadWithPreferences, showPreviewFileLoadedMessage, showUnsupportedFileMessage, hideAllNotifications]); }, [storeProjectInWorkingDir, setWorking, loadEdlFile, getEdlFilePath, getEdlFilePathOld, enableAskForImportChapters, ensureAccessToSourceDir, loadCutSegments, autoLoadTimecode, enableNativeHevc, ensureWritableOutDir, customOutDir, resetState, setCopyStreamIdsForPath, setFileFormat, outFormatLocked, setDetectedFileFormat, html5ifyAndLoadWithPreferences, showPreviewFileLoadedMessage, showUnsupportedFileMessage, hideAllNotifications]);
const toggleLastCommands = useCallback(() => setLastCommandsVisible(val => !val), []); const toggleLastCommands = useCallback(() => setLastCommandsVisible(val => !val), []);
const toggleSettings = useCallback(() => setSettingsVisible(val => !val), []); const toggleSettings = useCallback(() => setSettingsVisible(val => !val), []);
@ -2408,6 +2429,7 @@ const App = memo(() => {
onTunerRequested={onTunerRequested} onTunerRequested={onTunerRequested}
onKeyboardShortcutsDialogRequested={toggleKeyboardShortcuts} onKeyboardShortcutsDialogRequested={toggleKeyboardShortcuts}
askForCleanupChoices={askForCleanupChoices} askForCleanupChoices={askForCleanupChoices}
toggleStoreProjectInWorkingDir={toggleStoreProjectInWorkingDir}
/> />
</Table.Body> </Table.Body>
</Table> </Table>

@ -29,10 +29,11 @@ const Settings = memo(({
onTunerRequested, onTunerRequested,
onKeyboardShortcutsDialogRequested, onKeyboardShortcutsDialogRequested,
askForCleanupChoices, askForCleanupChoices,
toggleStoreProjectInWorkingDir,
}) => { }) => {
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, enableOverwriteOutput, setEnableOverwriteOutput, mouseWheelZoomModifierKey, setMouseWheelZoomModifierKey, captureFrameMethod, setCaptureFrameMethod, captureFrameQuality, setCaptureFrameQuality, captureFrameFileNameFormat, setCaptureFrameFileNameFormat, enableNativeHevc, setEnableNativeHevc, enableUpdateCheck, setEnableUpdateCheck, allowMultipleInstances, setAllowMultipleInstances } = 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, enableOverwriteOutput, setEnableOverwriteOutput, mouseWheelZoomModifierKey, setMouseWheelZoomModifierKey, captureFrameMethod, setCaptureFrameMethod, captureFrameQuality, setCaptureFrameQuality, captureFrameFileNameFormat, setCaptureFrameFileNameFormat, enableNativeHevc, setEnableNativeHevc, enableUpdateCheck, setEnableUpdateCheck, allowMultipleInstances, setAllowMultipleInstances } = useUserSettings();
const onLangChange = useCallback((e) => { const onLangChange = useCallback((e) => {
const { value } = e.target; const { value } = e.target;
@ -113,7 +114,7 @@ const Settings = memo(({
<Row> <Row>
<KeyCell>{t('Store project file (.llc) in the working directory or next to loaded media file?')}</KeyCell> <KeyCell>{t('Store project file (.llc) in the working directory or next to loaded media file?')}</KeyCell>
<Table.TextCell> <Table.TextCell>
<Button iconBefore={storeProjectInWorkingDir ? FolderCloseIcon : DocumentIcon} disabled={!autoSaveProjectFile} onClick={() => setStoreProjectInWorkingDir((v) => !v)}> <Button iconBefore={storeProjectInWorkingDir ? FolderCloseIcon : DocumentIcon} disabled={!autoSaveProjectFile} onClick={toggleStoreProjectInWorkingDir}>
{storeProjectInWorkingDir ? t('Store in working directory') : t('Store next to media file')} {storeProjectInWorkingDir ? t('Store in working directory') : t('Store next to media file')}
</Button> </Button>
</Table.TextCell> </Table.TextCell>

@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next';
import ExportModeButton from './components/ExportModeButton'; import ExportModeButton from './components/ExportModeButton';
import { withBlur, isMasBuild } from './util'; import { withBlur } from './util';
import { primaryTextColor, controlsBackground } from './colors'; import { primaryTextColor, controlsBackground } from './colors';
import useUserSettings from './hooks/useUserSettings'; import useUserSettings from './hooks/useUserSettings';
@ -21,8 +21,7 @@ const TopMenu = memo(({
const onOutFormatLockedClick = useCallback(() => setOutFormatLocked((v) => (v ? undefined : fileFormat)), [fileFormat, setOutFormatLocked]); const onOutFormatLockedClick = useCallback(() => setOutFormatLocked((v) => (v ? undefined : fileFormat)), [fileFormat, setOutFormatLocked]);
// We cannot allow exporting to a directory which has not yet been confirmed by an open dialog because of sandox restrictions const showClearWorkingDirButton = !!customOutDir;
const showClearWorkingDirButton = customOutDir && !isMasBuild;
function renderFormatLock() { function renderFormatLock() {
const Icon = outFormatLocked ? FaLock : FaUnlock; const Icon = outFormatLocked ? FaLock : FaUnlock;

@ -21,13 +21,14 @@ const simulateMasBuild = false;
const masMode = isMasBuild || simulateMasBuild; const masMode = isMasBuild || simulateMasBuild;
export default ({ customOutDir, setCustomOutDir }) => { export default ({ setCustomOutDir }) => {
const ensureAccessToSourceDir = useCallback(async (inputPath) => { const ensureAccessToSourceDir = useCallback(async (inputPath) => {
// Called if we need to read/write to the source file's directory (probably to read/write the project file) // Called if we need to read/write to the source file's directory (probably to read/write the project file)
const inputFileDir = getFileDir(inputPath); const inputFileDir = getFileDir(inputPath);
let simulateMasPermissionError = simulateMasBuild; let simulateMasPermissionError = simulateMasBuild;
// If we are MAS, we need to loop try to make the user confirm the dialog with the same path as the defaultPath.
for (;;) { for (;;) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
if (await checkDirWriteAccess(inputFileDir) && !simulateMasPermissionError) break; if (await checkDirWriteAccess(inputFileDir) && !simulateMasPermissionError) break;
@ -49,17 +50,21 @@ export default ({ customOutDir, setCustomOutDir }) => {
} }
}, []); }, []);
const ensureWritableOutDir = useCallback(async (inputPath) => { const ensureWritableOutDir = useCallback(async ({ inputPath, outDir }) => {
// we might need to change the output directory if the user chooses to give us a different one. // we might need to change the output directory if the user chooses to give us a different one.
let newCustomOutDir = customOutDir; let newCustomOutDir = outDir;
// Reset if working directory doesn't exist anymore if (newCustomOutDir) {
const customOutDirExists = await dirExists(customOutDir); // Reset if working directory doesn't exist anymore
if (!customOutDirExists) { const customOutDirExists = await dirExists(newCustomOutDir);
setCustomOutDir(undefined); if (!customOutDirExists) {
newCustomOutDir = undefined; setCustomOutDir(undefined);
newCustomOutDir = undefined;
}
} }
if (!newCustomOutDir && !inputPath) return newCustomOutDir;
const effectiveOutDirPath = getOutDir(newCustomOutDir, inputPath); const effectiveOutDirPath = getOutDir(newCustomOutDir, inputPath);
const hasDirWriteAccess = await checkDirWriteAccess(effectiveOutDirPath); const hasDirWriteAccess = await checkDirWriteAccess(effectiveOutDirPath);
if (!hasDirWriteAccess || simulateMasBuild) { if (!hasDirWriteAccess || simulateMasBuild) {
@ -80,7 +85,7 @@ export default ({ customOutDir, setCustomOutDir }) => {
} }
return newCustomOutDir; return newCustomOutDir;
}, [customOutDir, setCustomOutDir]); }, [setCustomOutDir]);
return { return {
ensureAccessToSourceDir, ensureAccessToSourceDir,

Loading…
Cancel
Save