@ -46,7 +46,7 @@ import configStore from './store';
import { save as edlStoreSave , load as edlStoreLoad } from './edlStore' ;
import {
getOutPath , formatDuration , toast , errorToast , showFfmpegFail , setFileNameTitle ,
promptTimeOffset , generateColor , getOutDir , withBlur , checkDirWriteAccess ,
promptTimeOffset , generateColor , getOutDir , withBlur , checkDirWriteAccess , dirExists ,
} from './util' ;
@ -474,17 +474,31 @@ const App = memo(() => {
}
} , [ setCutTime ] ) ;
const setOutputDir = useCallback ( async ( ) => {
const { filePaths } = await dialog . showOpenDialog ( { properties : [ 'openDirectory' ] } ) ;
setCustomOutDir ( ( filePaths && filePaths . length === 1 ) ? filePaths [ 0 ] : undefined ) ;
const outputDir = getOutDir ( customOutDir , filePath ) ;
const askForOutDir = useCallback ( async ( defaultPath ) => {
const { filePaths } = await dialog . showOpenDialog ( {
properties : [ 'openDirectory' ] ,
title : i18n . t ( 'Where do you want to save output files?' ) ,
message : i18n . t ( 'Where do you want to save output files? Make sure there is enough free space in this folder' ) ,
defaultPath ,
buttonLabel : i18n . t ( 'Select output folder' ) ,
} ) ;
return ( filePaths && filePaths . length === 1 ) ? filePaths [ 0 ] : undefined ;
} , [ ] ) ;
const changeOutDir = useCallback ( async ( ) => {
const newOutDir = await askForOutDir ( outputDir ) ;
/ / W e c a n n o t a l l o w e x p o r t i n g t o a d i r e c t o r y w h i c h h a s n o t y e t b e e n c o n f i r m e d b y a n o p e n d i a l o g
/ / b e c a u s e o f s a n d o x r e s t r i c t i o n s
if ( isMasBuild && ! newOutDir ) return ;
/ / E l s e i t ' s O K , w e a l l o w c l e a r i n g t h e d i r t o o
setCustomOutDir ( newOutDir ) ;
} , [ askForOutDir , outputDir ] ) ;
const effectiveFilePath = dummyVideoPath || html5FriendlyPath || filePath ;
const fileUri = effectiveFilePath ? filePathToUrl ( effectiveFilePath ) : '' ;
const outputDir = getOutDir ( customOutDir , filePath ) ;
const getEdlFilePath = useCallback ( ( fp ) => getOutPath ( customOutDir , fp , 'llc-edl.csv' ) , [ customOutDir ] ) ;
const edlFilePath = getEdlFilePath ( filePath ) ;
@ -774,26 +788,26 @@ const App = memo(() => {
toast . fire ( { timer : 10000 , icon : 'warning' , title : i18n . t ( 'This video is not natively supported' ) , text : i18n . t ( 'This means that there is no audio in the preview and it has low quality. The final export operation will however be lossless and contains audio!' ) } ) ;
}
const createDummyVideo = useCallback ( async ( fp) => {
const html5ifiedDummyPathDummy = getOutPath ( c ustomOutDir , fp , 'html5ified-dummy.mkv' ) ;
const createDummyVideo = useCallback ( async ( cod, fp) => {
const html5ifiedDummyPathDummy = getOutPath ( c od , fp , 'html5ified-dummy.mkv' ) ;
await html5ifyDummy ( fp , html5ifiedDummyPathDummy ) ;
setDummyVideoPath ( html5ifiedDummyPathDummy ) ;
setHtml5FriendlyPath ( ) ;
showUnsupportedFileMessage ( ) ;
} , [ customOutDir ] ) ;
} , [ ] ) ;
const tryCreateDummyVideo = useCallback ( async ( ) => {
try {
if ( working ) return ;
setWorking ( true ) ;
await createDummyVideo ( filePath) ;
await createDummyVideo ( customOutDir, filePath) ;
} catch ( err ) {
console . error ( err ) ;
errorToast ( i18n . t ( 'Failed to playback this file. Try to convert to friendly format from the menu' ) ) ;
} finally {
setWorking ( false ) ;
}
} , [ createDummyVideo , filePath , working ]) ;
} , [ createDummyVideo , filePath , working , customOutDir ]) ;
const togglePlay = useCallback ( ( resetPlaybackRate ) => {
if ( ! filePath ) return ;
@ -950,17 +964,7 @@ const App = memo(() => {
}
} , [ playing ] ) ;
const getHtml5ifiedPath = useCallback ( ( fp , type ) => getOutPath ( customOutDir , fp , ` html5ified- ${ type } .mp4 ` ) , [ customOutDir ] ) ;
const checkExistingHtml5FriendlyFile = useCallback ( async ( fp , speed ) => {
const existing = getHtml5ifiedPath ( fp , speed ) ;
const ret = existing && await exists ( existing ) ;
if ( ret ) {
setHtml5FriendlyPath ( existing ) ;
showUnsupportedFileMessage ( ) ;
}
return ret ;
} , [ getHtml5ifiedPath ] ) ;
const getHtml5ifiedPath = useCallback ( ( cod , fp , type ) => getOutPath ( cod , fp , ` html5ified- ${ type } .mp4 ` ) , [ ] ) ;
const loadEdlFile = useCallback ( async ( edlPath ) => {
try {
@ -982,8 +986,8 @@ const App = memo(() => {
}
} , [ cutSegmentsHistory , setCutSegments ] ) ;
const load = useCallback ( async ( fp , html5FriendlyPathRequested) => {
console . log ( 'Load' , { fp , html5FriendlyPathRequested } ) ;
const load = useCallback ( async ( { filePath : fp , customOutDir: cod , html5FriendlyPathRequested } ) => {
console . log ( 'Load' , { fp , cod, html5FriendlyPathRequested } ) ;
if ( working ) {
errorToast ( i18n . t ( 'Tried to load file while busy' ) ) ;
return ;
@ -993,6 +997,16 @@ const App = memo(() => {
setWorking ( true ) ;
async function checkExistingHtml5FriendlyFile ( speed ) {
const existing = getHtml5ifiedPath ( cod , fp , speed ) ;
const ret = existing && await exists ( existing ) ;
if ( ret ) {
setHtml5FriendlyPath ( existing ) ;
showUnsupportedFileMessage ( ) ;
}
return ret ;
}
try {
const fd = await getFormatData ( fp ) ;
@ -1028,10 +1042,10 @@ const App = memo(() => {
setHtml5FriendlyPath ( html5FriendlyPathRequested ) ;
showUnsupportedFileMessage ( ) ;
} else if (
! ( await checkExistingHtml5FriendlyFile ( fp , 'slow-audio' ) || await checkExistingHtml5FriendlyFile ( fp , 'slow' ) || await checkExistingHtml5FriendlyFile ( fp , 'fast' ) )
! ( await checkExistingHtml5FriendlyFile ( 'slow-audio' ) || await checkExistingHtml5FriendlyFile ( 'slow' ) || await checkExistingHtml5FriendlyFile ( 'fast' ) )
&& ! doesPlayerSupportFile ( streams )
) {
await createDummyVideo ( fp) ;
await createDummyVideo ( cod, fp) ;
}
await loadEdlFile ( getEdlFilePath ( fp ) ) ;
@ -1045,10 +1059,7 @@ const App = memo(() => {
} finally {
setWorking ( false ) ;
}
} , [
resetState , working , createDummyVideo , checkExistingHtml5FriendlyFile , loadEdlFile ,
getEdlFilePath ,
] ) ;
} , [ resetState , working , createDummyVideo , loadEdlFile , getEdlFilePath , getHtml5ifiedPath ] ) ;
const toggleHelp = useCallback ( ( ) => setHelpVisible ( val => ! val ) , [ ] ) ;
const toggleSettings = useCallback ( ( ) => setSettingsVisible ( val => ! val ) , [ ] ) ;
@ -1169,34 +1180,28 @@ const App = memo(() => {
const firstFile = filePaths [ 0 ] ;
const outDirPath = getOutDir ( customOutDir , firstFile ) ;
const customOutDirExists = await dirExists ( customOutDir ) ;
if ( ! customOutDirExists ) setCustomOutDir ( undefined ) ;
const newCustomOutDir = customOutDirExists ? customOutDir : undefined ;
const outDirPath = getOutDir ( newCustomOutDir , firstFile ) ;
const hasDirWriteAccess = await checkDirWriteAccess ( outDirPath ) ;
if ( ! hasDirWriteAccess ) {
if ( isMasBuild ) {
await Swal . fire ( {
title : i18n . t ( 'Mac OS file security' ) ,
icon : 'info' ,
text : i18n . t ( 'Mac OS requires you to choose the folder where the output files should be saved. This is only required the first time for each folder. Simply press "Open" in the next dialog to allow access to the default folder.' ) ,
} ) ;
/ / T O D O c h e c k t h a t c o r r e c t d i r i s s e l e c t e d
/ / T O D O a l s o w h e n c u s t o m o u t d i r c h a n g e s
/ / e s l i n t - d i s a b l e - n e x t - l i n e n o - u n u s e d - v a r s
const { canceled , filePaths : filePaths2 } = await dialog . showOpenDialog ( {
title : 'Select file to open' ,
defaultPath : outDirPath ,
properties : [ 'openDirectory' ] ,
} ) ;
if ( canceled ) return ;
const newOutDir = await askForOutDir ( outDirPath ) ;
/ / U s e r c a n c e l l e d o p e n d i a l o g , r e f u s e t o o p e n f i l 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 f r o m s a n d b o x
if ( ! newOutDir ) return ;
setCustomOutDir ( newOutDir ) ;
} else {
errorToast ( i18n . t ( 'You have no write access to the directory of this file, please select a custom working dir' ) ) ;
}
}
if ( ! isFileOpened ) {
load ( firstFile ) ;
load ( { filePath : firstFile , customOutDir : newCustomOutDir } ) ;
return ;
}
const { value } = await Swal . fire ( {
title : i18n . t ( 'You opened a new file. What do you want to do?' ) ,
icon : 'question' ,
@ -1211,12 +1216,12 @@ const App = memo(() => {
} ) ;
if ( value === 'open' ) {
load ( firstFile ) ;
load ( { filePath : firstFile , customOutDir : newCustomOutDir } ) ;
} else if ( value === 'add' ) {
addStreamSourceFile ( firstFile ) ;
setStreamsSelectorShown ( true ) ;
}
} , [ addStreamSourceFile , isFileOpened , load , mergeFiles , customOutDir ]) ;
} , [ addStreamSourceFile , isFileOpened , load , mergeFiles , customOutDir , askForOutDir ]) ;
const onDrop = useCallback ( async ( ev ) => {
ev . preventDefault ( ) ;
@ -1249,13 +1254,13 @@ const App = memo(() => {
try {
setWorking ( true ) ;
if ( [ 'fast' , 'slow' , 'slow-audio' ] . includes ( speed ) ) {
const html5FriendlyPath New = getHtml5ifiedPath ( filePath , speed ) ;
const html5FriendlyPath Requested = getHtml5ifiedPath ( customOutDir , filePath , speed ) ;
const encodeVideo = [ 'slow' , 'slow-audio' ] . includes ( speed ) ;
const encodeAudio = speed === 'slow-audio' ;
await ffmpegHtml5ify ( filePath , html5FriendlyPath New , encodeVideo , encodeAudio ) ;
load ( filePath , html5FriendlyPathNew ) ;
await ffmpegHtml5ify ( filePath , html5FriendlyPath Requested , encodeVideo , encodeAudio ) ;
load ( { filePath , html5FriendlyPathRequested , customOutDir } ) ;
} else {
await createDummyVideo ( filePath) ;
await createDummyVideo ( customOutDir, filePath) ;
}
} catch ( err ) {
errorToast ( i18n . t ( 'Failed to html5ify file' ) ) ;
@ -1373,7 +1378,7 @@ const App = memo(() => {
useEffect ( ( ) => {
document . body . addEventListener ( 'drop' , onDrop ) ;
return ( ) => document . body . removeEventListener ( 'drop' , onDrop ) ;
} , [ load, mergeFiles , onDrop] ) ;
} , [ onDrop] ) ;
const commonFormatsMap = useMemo ( ( ) => fromPairs ( commonFormats . map ( format => [ format , allOutFormats [ format ] ] )
@ -1433,7 +1438,7 @@ const App = memo(() => {
const renderSettings = useCallback ( ( ) => (
< Settings
setOutputDir= { setOutp utDir}
changeOutDir= { changeO utDir}
customOutDir = { customOutDir }
autoMerge = { autoMerge }
setAutoMerge = { setAutoMerge }
@ -1455,7 +1460,7 @@ const App = memo(() => {
renderCaptureFormatButton = { renderCaptureFormatButton }
onWheelTunerRequested = { onWheelTunerRequested }
/ >
) , [ AutoExportToggler , askBeforeClose , autoMerge , autoSaveProjectFile , customOutDir , invertCutSegments , keyframeCut , renderCaptureFormatButton , renderOutFmt , timecodeShowFrames , setOutp utDir, onWheelTunerRequested , language ] ) ;
) , [ AutoExportToggler , askBeforeClose , autoMerge , autoSaveProjectFile , customOutDir , invertCutSegments , keyframeCut , renderCaptureFormatButton , renderOutFmt , timecodeShowFrames , changeO utDir, onWheelTunerRequested , language ] ) ;
useEffect ( ( ) => {
if ( ! isStoreBuild ) loadMifiLink ( ) . then ( setMifiLink ) ;
@ -1463,7 +1468,7 @@ const App = memo(() => {
useEffect ( ( ) => {
/ / T e s t i n g :
/ / i f ( i s D e v ) l o a d ( '/ U s e r s / m i f i / D o w n l o a d s / i n p . M O V ' ) ;
/ / i f ( i s D e v ) l o a d ( { f i l e P a t h : '/ U s e r s / m i f i / D o w n l o a d s / i n p . M O V ' , c u s t o m O u t D i r } ) ;
/ / e s l i n t - d i s a b l e - n e x t - l i n e r e a c t - h o o k s / e x h a u s t i v e - d e p s
} , [ ] ) ;
@ -1527,7 +1532,7 @@ const App = memo(() => {
copyAnyAudioTrack = { copyAnyAudioTrack }
toggleStripAudio = { toggleStripAudio }
customOutDir = { customOutDir }
setOutputDir= { setOutp utDir}
changeOutDir= { changeO utDir}
renderOutFmt = { renderOutFmt }
outSegments = { outSegments }
autoMerge = { autoMerge }