@ -56,7 +56,7 @@ import {
import { exportEdlFile , readEdlFile , saveLlcProject , loadLlcProject , askForEdlImport } from './edlStore' ;
import { formatYouTube , getTimeFromFrameNum as getTimeFromFrameNumRaw , getFrameCountRaw } from './edlFormats' ;
import {
getOutPath , toast , errorToast , handleError , setFileNameTitle , getOutDir , withBlur,
getOutPath , toast , errorToast , handleError , setFileNameTitle , getOutDir , getFileDir, withBlur,
checkDirWriteAccess , dirExists , openDirToast , isMasBuild , isStoreBuild , dragPreventer , doesPlayerSupportFile ,
isDurationValid , filenamify , getOutFileExtension , generateSegFileName , defaultOutSegTemplate ,
havePermissionToReadFile , resolvePathIfNeeded , getPathReadAccessError , html5ifiedPrefix , html5dummySuffix , findExistingHtml5FriendlyFile ,
@ -64,7 +64,7 @@ import {
} from './util' ;
import { formatDuration } from './util/duration' ;
import { adjustRate } from './util/rate-calculator' ;
import { askForOutDir , askForI mportChapters, createNumSegments as createNumSegmentsDialog , createFixedDurationSegments as createFixedDurationSegmentsDialog , promptTimeOffset , askForHtml5ifySpeed , askForFileOpenAction , confirmExtractAllStreamsDialog , showCleanupFilesDialog , showDiskFull , showCutFailedDialog , labelSegmentDialog , openYouTubeChaptersDialog , openAbout , showEditableJsonDialog } from './dialogs' ;
import { askForOutDir , askForI nputDir, askForI mportChapters, createNumSegments as createNumSegmentsDialog , createFixedDurationSegments as createFixedDurationSegmentsDialog , promptTimeOffset , askForHtml5ifySpeed , askForFileOpenAction , confirmExtractAllStreamsDialog , showCleanupFilesDialog , showDiskFull , showCutFailedDialog , labelSegmentDialog , openYouTubeChaptersDialog , openAbout , showEditableJsonDialog } from './dialogs' ;
import { openSendReportDialog } from './reporting' ;
import { fallbackLng } from './i18n' ;
import { createSegment , getCleanCutSegments , getSegApparentStart , findSegmentsAtCursor , sortSegments , invertSegments , getSegmentTags } from './segments' ;
@ -616,7 +616,37 @@ 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' ) } ) ;
} , [ hideAllNotifications , fileFormat ] ) ;
const ensureOutDirAccessible = useCallback ( async ( outFilePath ) => {
const ensureAccessibleDirectories = useCallback ( async ( { inputPath , checkInputDir } ) => {
/ / M a c O S A p p S t o r e b u i l d s d o n ' t a l l o w w r i t i n g a n y w h e r e , a n d w e s e t t h e f l a g c o m . a p p l e . s e c u r i t y . f i l e s . u s e r - s e l e c t e d . r e a d - w r i t e
/ / W i t h t h i s f l a g , w e c a n s h o w t h e u s e r a n o p e n - d i a l o g f o r a d i r e c t o r y , a n d o n c e t h e u s e r h a s o p e n e d t h a t d i r e c t o r y , w e c a n w r i t e f i l e s t h e r e u n t i l t h e a p p i s r e s t a r t e d .
/ / N O T E : w h e n M A S ( d e v ) b u i l d , A p p l i c a t i o n S u p p o r t w i l l i n s t e a d b e h e r e :
/ / ~ / L i b r a r y / C o n t a i n e r s / n o . m i f i . l o s s l e s s c u t - m a c / D a t a / L i b r a r y / A p p l i c a t i o n S u p p o r t
/ / T o s t a r t f r o m s c r a t c h : r m - r f ~ / L i b r a r y / C o n t a i n e r s / n o . m i f i . l o s s l e s s c u t - m a c
const simulateMasBuild = false ; / / i s D e v ; / / c a n b e u s e d f o r t e s t i n g w i t h o u t h a v i n g t o b u i l d m a s - d e v
const masMode = isMasBuild || simulateMasBuild ;
if ( checkInputDir ) {
/ / C h e c k i n p u t d i r , i f w e n e e d t o w r i t e p r o j e c t f i l e h e r e
const inputFileDir = getFileDir ( inputPath ) ;
let simulateMasPermissionError = simulateMasBuild ;
for ( ; ; ) {
/ / e s l i n t - d i s a b l e - n e x t - l i n e n o - a w a i t - i n - l o o p
if ( await checkDirWriteAccess ( inputFileDir ) && ! simulateMasPermissionError ) break ;
if ( ! masMode ) {
/ / f a i l r i g h t a w a y
errorToast ( i18n . t ( 'You have no write access to the directory of this file' ) ) ;
return { cancel : true } ;
}
/ / W e a r e n o w m a s , s o w e n e e d t o t r y t o f o r c e t h e u s e r t o a l l o w a c c e s s t o t h e d i r , s o w e c a n w r i t e t h e p r o j e c t f i l e l a t e r
/ / e s l i n t - d i s a b l e - n e x t - l i n e n o - a w a i t - i n - l o o p
const userSelectedDir = await askForInputDir ( inputFileDir ) ;
simulateMasPermissionError = false ; / / a s s u m e u s e r c h o s e t h e r i g h t d i r
if ( userSelectedDir == null ) return { cancel : true } ; / / a l l o w u s e r t o c a n c e l
}
}
let newCustomOutDir = customOutDir ;
/ / R e s e t i f d o e s n ' t e x i s t a n y m o r e
@ -626,10 +656,10 @@ const App = memo(() => {
newCustomOutDir = undefined ;
}
const effectiveOutDirPath = getOutDir ( newCustomOutDir , outFile Path) ;
const effectiveOutDirPath = getOutDir ( newCustomOutDir , input Path) ;
const hasDirWriteAccess = await checkDirWriteAccess ( effectiveOutDirPath ) ;
if ( ! hasDirWriteAccess ) {
if ( isMasBuild ) {
if ( ! hasDirWriteAccess || simulateMasBuild ) {
if ( masMode ) {
const newOutDir = await askForOutDir ( effectiveOutDirPath ) ;
/ / I f u s e r c a n c e l e d o p e n d i a l o g , r e f u s e t o c o n t i n u e , b e c a u s e w e w i l l g e t p e r m i s s i o n d e n i e d e r r o r f r o m M A S s a n d b o x
if ( ! newOutDir ) return { cancel : true } ;
@ -638,6 +668,7 @@ const App = memo(() => {
} else {
errorToast ( i18n . t ( 'You have no write access to the directory of this file, please select a custom working dir' ) ) ;
setCustomOutDir ( undefined ) ;
newCustomOutDir = undefined ;
return { cancel : true } ;
}
}
@ -652,7 +683,7 @@ const App = memo(() => {
setWorking ( i18n . t ( 'Merging' ) ) ;
const firstPath = paths [ 0 ] ;
const { newCustomOutDir , cancel } = await ensure OutDir Accessible( firstPath ) ;
const { newCustomOutDir , cancel } = await ensure AccessibleDirectories ( { inputPath : firstPath } ) ;
if ( cancel ) return ;
const ext = extname ( firstPath ) ;
@ -679,7 +710,7 @@ const App = memo(() => {
setWorking ( ) ;
setCutProgress ( ) ;
}
} , [ setWorking , ensure OutDir Accessible, customOutDir , segmentsToChapters , ffmpegMergeFiles , ffmpegExperimental , preserveMovData , movFastStart , preserveMetadataOnMerge ] ) ;
} , [ setWorking , ensure AccessibleDirectories , customOutDir , segmentsToChapters , ffmpegMergeFiles , ffmpegExperimental , preserveMovData , movFastStart , preserveMetadataOnMerge ] ) ;
const concatCurrentBatch = useCallback ( ( ) => {
if ( batchFiles . length < 2 ) {
@ -952,11 +983,8 @@ const App = memo(() => {
for ( const path of filePaths ) {
try {
/ / e s l i n t - d i s a b l e - n e x t - l i n e n o - a w a i t - i n - l o o p
const { newCustomOutDir , cancel } = await ensureOutDirAccessible ( path ) ;
if ( cancel ) {
toast . fire ( { title : i18n . t ( 'Aborted' ) } ) ;
return ;
}
const { newCustomOutDir , cancel } = await ensureAccessibleDirectories ( { inputPath : path } ) ;
if ( cancel ) return ;
/ / e s l i n t - d i s a b l e - n e x t - l i n e n o - a w a i t - i n - l o o p
await html5ify ( { customOutDir : newCustomOutDir , filePath : path , speed , hasAudio : true , hasVideo : true , onProgress : setTotalProgress } ) ;
@ -977,7 +1005,7 @@ const App = memo(() => {
setWorking ( ) ;
setCutProgress ( ) ;
}
} , [ batchFiles , ensure OutDir Accessible, html5ify , setWorking ] ) ;
} , [ batchFiles , ensure AccessibleDirectories , html5ify , setWorking ] ) ;
const getConvertToSupportedFormat = useCallback ( ( fallback ) => rememberConvertToSupportedFormat || fallback , [ rememberConvertToSupportedFormat ] ) ;
@ -1518,11 +1546,12 @@ const App = memo(() => {
return ;
}
const { newCustomOutDir , cancel } = await ensureOutDirAccessible ( path ) ;
/ / c h e c k I n p u t D i r : t r u e b e c a u s e w e w i l l b e w r i t i n g p r o j e c t f i l e h e r e
const { newCustomOutDir , cancel } = await ensureAccessibleDirectories ( { inputPath : path , checkInputDir : true } ) ;
if ( cancel ) return ;
await loadMedia ( { filePath : path , customOutDir : newCustomOutDir , projectPath } ) ;
} , [ ensure OutDir Accessible, loadMedia ] ) ;
} , [ ensure AccessibleDirectories , loadMedia ] ) ;
const batchOpenSingleFile = useCallback ( async ( path ) => {
if ( workingRef . current ) return ;