@ -54,10 +54,10 @@ import {
import { exportEdlFile , readEdlFile , saveLlcProject , loadLlcProject , readEdl } from './edlStore' ;
import { formatYouTube } from './edlFormats' ;
import {
getOutPath , toast , errorToast , handleError , s howFfmpegFail, s etFileNameTitle, getOutDir , withBlur ,
getOutPath , toast , errorToast , handleError , s etFileNameTitle, getOutDir , withBlur ,
checkDirWriteAccess , dirExists , openDirToast , isMasBuild , isStoreBuild , dragPreventer , doesPlayerSupportFile ,
isDurationValid , isWindows , filenamify , getOutFileExtension , generateSegFileName , defaultOutSegTemplate ,
hasDuplicates , havePermissionToReadFile , isMac , getFileBaseName, resolvePathIfNeeded, pathExists ,
hasDuplicates , havePermissionToReadFile , isMac , resolvePathIfNeeded, pathExists , html5ifiedPrefix , html5dummySuffix , findExistingHtml5FriendlyFile ,
} from './util' ;
import { formatDuration } from './util/duration' ;
import { askForOutDir , askForImportChapters , createNumSegments , createFixedDurationSegments , promptTimeOffset , askForHtml5ifySpeed , askForFileOpenAction , confirmExtractAllStreamsDialog , cleanupFilesDialog , showDiskFull , showCutFailedDialog , labelSegmentDialog , openYouTubeChaptersDialog , showMergeDialog , showOpenAndMergeDialog , openAbout , showEditableJsonDialog } from './dialogs' ;
@ -65,14 +65,20 @@ import { openSendReportDialog } from './reporting';
import { fallbackLng } from './i18n' ;
import { createSegment , createInitialCutSegments , getCleanCutSegments , getSegApparentStart , findSegmentsAtCursor , sortSegments , invertSegments , getSegmentTags } from './segments' ;
import loadingLottie from './7077-magic-flow.json' ;
function getHtml5ifiedPath ( cod , fp , type ) {
/ / S e e a l s o i n s i d e f f m p e g H t m l 5 i f y
const ext = ( isMac && [ 'slowest' , 'slow' , 'slow-audio' ] . includes ( type ) ) ? 'mp4' : 'mkv' ;
return getOutPath ( cod , fp , ` ${ html5ifiedPrefix } ${ type } . ${ ext } ` ) ;
}
const isDev = window . require ( 'electron-is-dev' ) ;
const electron = window . require ( 'electron' ) ; / / e s l i n t - d i s a b l e - l i n e
const trash = window . require ( 'trash' ) ;
const { unlink , exists , readdir } = window . require ( 'fs-extra' ) ;
const { unlink , exists } = window . require ( 'fs-extra' ) ;
const { extname , parse : parsePath , sep : pathSep , join : pathJoin , normalize : pathNormalize , basename , dirname } = window . require ( 'path' ) ;
const { dialog } = electron . remote ;
@ -134,6 +140,7 @@ const App = memo(() => {
const [ showSideBar , setShowSideBar ] = useState ( true ) ;
const [ hideCanvasPreview , setHideCanvasPreview ] = useState ( false ) ;
const [ cleanupChoices , setCleanupChoices ] = useState ( { tmpFiles : true } ) ;
const [ rememberConvertToSupportedFormat , setRememberConvertToSupportedFormat ] = useState ( ) ;
/ / S e g m e n t r e l a t e d s t a t e
const [ currentSegIndex , setCurrentSegIndex ] = useState ( 0 ) ;
@ -275,10 +282,6 @@ const App = memo(() => {
seekRel ( ( 1 / ( detectedFps || 60 ) ) * dir ) ;
} , [ seekRel , detectedFps ] ) ;
/ * u s e E f f e c t ( ( ) = > ( ) = > {
if ( usingDummyVideo && previewFilePath ) unlink ( previewFilePath ) . catch ( console . error ) ;
} , [ usingDummyVideo , previewFilePath ] ) ; * /
/ / 3 6 0 m e a n s w e d o n ' t m o d i f y r o t a t i o n
const isRotationSet = rotation !== 360 ;
const effectiveRotation = isRotationSet ? rotation : ( mainVideoStream && mainVideoStream . tags && mainVideoStream . tags . rotate && parseInt ( mainVideoStream . tags . rotate , 10 ) ) ;
@ -502,6 +505,7 @@ const App = memo(() => {
setCustomOutDir ( ) ;
} , [ setCustomOutDir ] ) ;
const usingPreviewFile = ! ! previewFilePath ;
const effectiveFilePath = previewFilePath || filePath ;
const fileUri = effectiveFilePath ? filePathToUrl ( effectiveFilePath ) : '' ;
@ -600,6 +604,7 @@ const App = memo(() => {
} , [ customOutDir , setCustomOutDir ] ) ;
const mergeFiles = useCallback ( async ( { paths , allStreams , segmentsToChapters : segmentsToChapters2 } ) => {
if ( working ) return ;
try {
setWorking ( i18n . t ( 'Merging' ) ) ;
@ -627,7 +632,7 @@ const App = memo(() => {
setWorking ( ) ;
setCutProgress ( ) ;
}
} , [ assureOutDirAccess , ffmpegExperimental , preserveMovData , movFastStart , preserveMetadataOnMerge , customOutDir , ffmpegMergeFiles ]) ;
} , [ assureOutDirAccess , ffmpegExperimental , preserveMovData , movFastStart , preserveMetadataOnMerge , customOutDir , ffmpegMergeFiles , working ]) ;
const toggleCaptureFormat = useCallback ( ( ) => setCaptureFormat ( f => ( f === 'png' ? 'jpeg' : 'png' ) ) , [ setCaptureFormat ] ) ;
@ -755,10 +760,10 @@ const App = memo(() => {
video . currentTime = 0 ;
video . playbackRate = 1 ;
/ / s e t W o r k i n g ( ) ;
setFileNameTitle ( ) ;
setPreviewFilePath ( ) ;
setUsingDummyVideo ( false ) ;
setWorking ( ) ;
setPlaying ( false ) ;
setDuration ( ) ;
cutSegmentsHistory . go ( 0 ) ;
@ -802,36 +807,62 @@ const App = memo(() => {
if ( ! hideAllNotifications ) toast . fire ( { text : i18n . t ( 'Loaded existing preview file: {{ fileName }}' , { fileName } ) } ) ;
} , [ hideAllNotifications ] ) ;
const html5ifiedPrefix = 'html5ified-' ;
const html5dummySuffix = 'dummy' ;
const html5ify = useCallback ( async ( { customOutDir : cod , filePath : fp , speed , hasAudio : ha , hasVideo : hv } ) => {
const path = getHtml5ifiedPath ( cod , fp , speed ) ;
let audio ;
if ( ha ) {
if ( speed === 'slowest' ) audio = 'hq' ;
else if ( [ 'slow-audio' , 'fastest-audio' ] . includes ( speed ) ) audio = 'lq' ;
else if ( [ 'fast-audio' , 'fastest-audio-remux' ] . includes ( speed ) ) audio = 'copy' ;
}
let video ;
if ( hv ) {
if ( speed === 'slowest' ) video = 'hq' ;
else if ( [ 'slow-audio' , 'slow' ] . includes ( speed ) ) video = 'lq' ;
else video = 'copy' ;
}
const createDummyVideo = useCallback ( async ( cod , fp ) => {
const html5ifiedDummyPath = getOutPath ( cod , fp , ` ${ html5ifiedPrefix } ${ html5dummySuffix } .mkv ` ) ;
try {
setCutProgress ( 0 ) ;
await html5ifyDummy ( { filePath : fp , outPath : html5ifiedDummyPath , onProgress : setCutProgress } ) ;
await ffmpegHtml5ify ( { filePath : fp , outPath : path , video , audio , onProgress : setCutProgress } ) ;
} finally {
setCutProgress ( ) ;
}
setUsingDummyVideo ( true ) ;
setPreviewFilePath ( html5ifiedDummyPath ) ;
showUnsupportedFileMessage ( ) ;
} , [ html5ifyDummy , showUnsupportedFileMessage ] ) ;
return path ;
} , [ ffmpegHtml5ify ] ) ;
const showPlaybackFailedMessage = ( ) => errorToast ( i18n . t ( 'Unable to playback this file. Try to convert to supported format from the menu' ) ) ;
const html5ifyAndLoad = useCallback ( async ( cod , fp , speed , hv , ha ) => {
const usesDummyVideo = [ 'fastest-audio' , 'fastest-audio-remux' , 'fastest' ] . includes ( speed ) ;
console . log ( 'html5ifyAndLoad' , { speed , hasVideo : hv , hasAudio : ha , usesDummyVideo } ) ;
const tryCreateDummyVideo = useCallback ( async ( ) => {
try {
if ( working ) return ;
setWorking ( i18n . t ( 'Converting to supported format' ) ) ;
await createDummyVideo ( customOutDir , filePath ) ;
} catch ( err ) {
console . error ( err ) ;
showPlaybackFailedMessage ( ) ;
} finally {
setWorking ( ) ;
async function doHtml5ify ( ) {
if ( speed === 'fastest' ) {
const path = getOutPath ( cod , fp , ` ${ html5ifiedPrefix } ${ html5dummySuffix } .mkv ` ) ;
try {
setCutProgress ( 0 ) ;
await html5ifyDummy ( { filePath : fp , outPath : path , onProgress : setCutProgress } ) ;
} finally {
setCutProgress ( ) ;
}
return path ;
}
if ( [ 'fastest-audio' , 'fastest-audio-remux' , 'fast-audio' , 'fast' , 'slow' , 'slow-audio' , 'slowest' ] . includes ( speed ) ) {
const shouldIncludeVideo = ! usesDummyVideo && hv ;
const path = await html5ify ( { customOutDir : cod , filePath : fp , speed , hasAudio : ha , hasVideo : shouldIncludeVideo } ) ;
return path ;
}
return undefined ;
}
} , [ createDummyVideo , filePath , working , customOutDir ] ) ;
const path = await doHtml5ify ( ) ;
if ( ! path ) return ;
setPreviewFilePath ( path ) ;
setUsingDummyVideo ( usesDummyVideo ) ;
showUnsupportedFileMessage ( ) ;
} , [ html5ify , html5ifyDummy , showUnsupportedFileMessage ] ) ;
const showPlaybackFailedMessage = ( ) => errorToast ( i18n . t ( 'Unable to playback this file. Try to convert to supported format from the menu' ) ) ;
const togglePlay = useCallback ( ( resetPlaybackRate ) => {
if ( ! filePath ) return ;
@ -860,6 +891,8 @@ const App = memo(() => {
} , [ askBeforeClose , isFileOpened , resetState , working ] ) ;
const cleanupFiles = useCallback ( async ( ) => {
if ( working ) return ;
/ / B e c a u s e w e w i l l r e s e t s t a t e b e f o r e d e l e t i n g f i l e s
const saved = { previewFilePath , filePath , edlFilePath } ;
@ -908,7 +941,7 @@ const App = memo(() => {
} finally {
setWorking ( ) ;
}
} , [ filePath , previewFilePath , closeFile , edlFilePath , cleanupChoices ]) ;
} , [ filePath , previewFilePath , closeFile , edlFilePath , cleanupChoices , working ]) ;
const outSegments = useMemo ( ( ) => ( invertCutSegments ? inverseCutSegments : apparentCutSegments ) ,
[ invertCutSegments , inverseCutSegments , apparentCutSegments ] ) ;
@ -1087,7 +1120,7 @@ const App = memo(() => {
return ;
}
showFfmpegFail ( err ) ;
handleError ( err ) ;
} finally {
setWorking ( ) ;
setCutProgress ( ) ;
@ -1145,12 +1178,6 @@ const App = memo(() => {
}
} , [ playing , canvasPlayerEnabled ] ) ;
const getHtml5ifiedPath = useCallback ( ( cod , fp , type ) => {
/ / S e e a l s o i n s i d e f f m p e g H t m l 5 i f y
const ext = ( isMac && [ 'slowest' , 'slow' , 'slow-audio' ] . includes ( type ) ) ? 'mp4' : 'mkv' ;
return getOutPath ( cod , fp , ` ${ html5ifiedPrefix } ${ type } . ${ ext } ` ) ;
} , [ ] ) ;
const firstSegmentAtCursorIndex = useMemo ( ( ) => {
const segmentsAtCursorIndexes = findSegmentsAtCursor ( apparentCutSegments , commandedTime ) ;
return segmentsAtCursorIndexes [ 0 ] ;
@ -1196,66 +1223,51 @@ const App = memo(() => {
} , [ setCutSegments ] ) ;
const loadEdlFile = useCallback ( async ( path , type ) => {
try {
const edl = await readEdlFile ( { type , path } ) ;
loadCutSegments ( edl ) ;
} catch ( err ) {
console . error ( 'EDL load failed' , err ) ;
errorToast ( ` ${ i18n . t ( 'Failed to load segments' ) } ( ${ err . message } ) ` ) ;
}
console . log ( 'Loading EDL file' , type , path ) ;
loadCutSegments ( await readEdlFile ( { type , path } ) ) ;
} , [ loadCutSegments ] ) ;
const load = useCallback ( async ( { filePath : fp , customOutDir : cod , html5FriendlyPathRequested , dummyVideoPathRequested , projectPath } ) => {
console . log ( 'Load' , { fp , cod , html5FriendlyPathRequested , dummyVideoPathRequested , projectPath } ) ;
if ( working ) return ;
const load = useCallback ( async ( { filePath : fp , customOutDir : cod , projectPath } ) => {
console . log ( 'Load' , { fp , cod , projectPath } ) ;
resetState ( ) ;
setWorking ( i18n . t ( 'Loading file' ) ) ;
async function checkAndSetExistingHtml5FriendlyFile ( ) {
const speeds = [ 'slowest' , 'slow-audio' , 'slow' , 'fast-audio' , 'fast' , 'fastest-audio' , 'fastest-audio-remux' , html5dummySuffix ] ;
const prefix = ` ${ getFileBaseName ( fp ) } - ${ html5ifiedPrefix } ` ;
const outDir = getOutDir ( cod , fp ) ;
const dirEntries = await readdir ( outDir ) ;
let speed ;
let path ;
/ / e s l i n t - d i s a b l e - n e x t - l i n e n o - r e s t r i c t e d - s y n t a x
for ( const entry of dirEntries ) {
const prefixMatch = entry . startsWith ( prefix ) ;
if ( prefixMatch ) {
const speedMatch = speeds . find ( ( s ) => new RegExp ( ` ${ s } \\ ..* $ ` ) . test ( entry . replace ( prefix , '' ) ) ) ;
if ( ! speedMatch || speedMatch !== html5dummySuffix ) { / / s k i p d u m m y , a s i t ' s n o t v e r y u s e f u l
path = pathJoin ( outDir , entry ) ;
if ( speedMatch ) speed = speedMatch ; / / W e w a n t t o a l s o c a p t u r e a n y c u s t o m u s e r s u f f i x ( b u t N O T d u m m y )
break ;
}
const res = await findExistingHtml5FriendlyFile ( fp , cod ) ;
if ( ! res ) return false ;
console . log ( 'Found existing supported file' , res . path ) ;
setUsingDummyVideo ( res . usingDummyVideo ) ;
setPreviewFilePath ( res . path ) ;
showPreviewFileLoadedMessage ( basename ( res . path ) ) ;
return true ;
}
async function getFileMeta ( ) {
try {
const fd = await getFormatData ( fp ) ;
const ff = await getDefaultOutFormat ( fp , fd ) ;
const allStreamsResponse = await getAllStreams ( fp ) ;
const { streams } = allStreamsResponse ;
/ / c o n s o l e . l o g ( s t r e a m s , f d , f f ) ;
return { fd , ff , streams } ;
} catch ( err ) {
/ / W i n d o w s w i l l t h r o w e r r o r w i t h c o d e E N O E N T i f f o r m a t d e t e c t i o n f a i l s .
if ( err . exitCode === 1 || ( isWindows && err . code === 'ENOENT' ) ) {
const err2 = new Error ( ` Unsupported file: ${ err . message } ` ) ;
err2 . code = 'LLC_FFPROBE_UNSUPPORTED_FILE' ;
throw err2 ;
}
throw err ;
}
if ( ! path ) return false ;
console . log ( 'Found existing supported file' , path , speed ) ;
setUsingDummyVideo ( [ 'fastest-audio' , 'fastest-audio-remux' ] . includes ( speed ) ) ;
setPreviewFilePath ( path ) ;
showPreviewFileLoadedMessage ( basename ( path ) ) ;
return true ;
}
try {
const fd = await getFormatData ( fp ) ;
const { ff , fd , streams } = await getFileMeta ( ) ;
const ff = await getDefaultOutFormat ( fp , fd ) ;
if ( ! ff ) {
errorToast ( i18n . t ( 'Unable to determine file format' ) ) ;
return ;
}
const { streams } = await getAllStreams ( fp ) ;
/ / c o n s o l e . l o g ( ' s t r e a m s ' , s t r e a m s N e w ) ;
if ( ! ff ) throw new Error ( 'Unable to determine file format' ) ;
if ( autoLoadTimecode ) {
const timecode = getTimecodeFromStreams ( streams ) ;
@ -1294,40 +1306,38 @@ const App = memo(() => {
}
const validDuration = isDurationValid ( parseFloat ( fd . duration ) ) ;
const hasLoadedExistingHtml5FriendlyFile = await checkAndSetExistingHtml5FriendlyFile ( ) ;
if ( html5FriendlyPathRequested ) {
setUsingDummyVideo ( false ) ;
setPreviewFilePath ( html5FriendlyPathRequested ) ;
showUnsupportedFileMessage ( ) ;
} else if ( dummyVideoPathRequested ) {
setUsingDummyVideo ( true ) ;
setPreviewFilePath ( dummyVideoPathRequested ) ;
showUnsupportedFileMessage ( ) ;
} else if (
! ( await checkAndSetExistingHtml5FriendlyFile ( ) )
&& ! doesPlayerSupportFile ( streams )
&& validDuration
) {
await createDummyVideo ( cod , fp ) ;
/ / ' f a s t e s t ' w o r k s w i t h a l m o s t a l l v i d e o f i l e s
if ( ! hasLoadedExistingHtml5FriendlyFile && ! doesPlayerSupportFile ( streams ) && validDuration ) {
setWorking ( i18n . t ( 'Converting to supported format' ) ) ;
await html5ifyAndLoad ( cod , fp , rememberConvertToSupportedFormat || 'fastest' , ! ! videoStream , ! ! audioStream ) ;
}
const openedFileEdlPath = getEdlFilePath ( fp ) ;
const openedFileEdlPathOld = getEdlFilePathOld ( fp ) ;
if ( projectPath ) {
await loadEdlFile ( projectPath , 'llc' ) ;
} else if ( await exists ( openedFileEdlPath ) ) {
await loadEdlFile ( openedFileEdlPath , 'llc' ) ;
} else if ( await exists ( openedFileEdlPathOld ) ) {
await loadEdlFile ( openedFileEdlPathOld , 'csv' ) ;
} else {
const edl = await tryReadChaptersToEdl ( fp ) ;
if ( edl . length > 0 && enableAskForImportChapters && ( await askForImportChapters ( ) ) ) {
console . log ( 'Read chapters' , edl ) ;
loadCutSegments ( edl ) ;
try {
const openedFileEdlPath = getEdlFilePath ( fp ) ;
const openedFileEdlPathOld = getEdlFilePathOld ( fp ) ;
if ( projectPath ) {
await loadEdlFile ( projectPath , 'llc' ) ;
} else if ( await exists ( openedFileEdlPath ) ) {
await loadEdlFile ( openedFileEdlPath , 'llc' ) ;
} else if ( await exists ( openedFileEdlPathOld ) ) {
await loadEdlFile ( openedFileEdlPathOld , 'csv' ) ;
} else {
const edl = await tryReadChaptersToEdl ( fp ) ;
if ( edl . length > 0 && enableAskForImportChapters && ( await askForImportChapters ( ) ) ) {
console . log ( 'Read chapters' , edl ) ;
loadCutSegments ( edl ) ;
}
}
} catch ( err ) {
console . error ( 'EDL load failed, but continuing' , err ) ;
errorToast ( ` ${ i18n . t ( 'Failed to load segments' ) } ( ${ err . message } ) ` ) ;
}
/ / t h r o w n e w E r r o r ( ' t e s t ' ) ;
if ( ! validDuration ) toast . fire ( { icon : 'warning' , timer : 10000 , text : i18n . t ( 'This file does not have a valid duration. This may cause issues. You can try to fix the file\'s duration from the File menu' ) } ) ;
/ / T h i s n e e d s t o b e l a s t , b e c a u s e i t t r i g g e r s < v i d e o > t o l o a d t h e v i d e o
@ -1335,17 +1345,10 @@ const App = memo(() => {
/ / h t t p s : / / g i t h u b . c o m / m i f i / l o s s l e s s - c u t / i s s u e s / 5 1 5
setFilePath ( fp ) ;
} catch ( err ) {
/ / W i n d o w s w i l l t h r o w e r r o r w i t h c o d e E N O E N T i f f o r m a t d e t e c t i o n f a i l s .
if ( err . exitCode === 1 || ( isWindows && err . code === 'ENOENT' ) ) {
errorToast ( i18n . t ( 'Unsupported file' ) ) ;
console . error ( err ) ;
return ;
}
showFfmpegFail ( err ) ;
} finally {
setWorking ( ) ;
resetState ( ) ;
throw err ;
}
} , [ resetState , working, createDummyVideo , loadEdlFile , getEdlFilePath , getEdlFilePathOld , loadCutSegments , enableAskForImportChapters , showUnsupportedFileMessage, autoLoadTimecode, outFormatLocked , showPreviewFileLoadedMessage ] ) ;
} , [ resetState , html5ifyAndLoad , loadEdlFile , getEdlFilePath , getEdlFilePathOld , loadCutSegments , enableAskForImportChapters , autoLoadTimecode , outFormatLocked , showPreviewFileLoadedMessage , rememberConvertToSupportedFormat ] ) ;
const toggleHelp = useCallback ( ( ) => setHelpVisible ( val => ! val ) , [ ] ) ;
const toggleSettings = useCallback ( ( ) => setSettingsVisible ( val => ! val ) , [ ] ) ;
@ -1504,7 +1507,7 @@ const App = memo(() => {
} , [ customOutDir , filePath , mainStreams , outputDir , working ] ) ;
const extractAllStreams = useCallback ( async ( ) => {
if ( ! filePath ) return ;
if ( ! filePath || working ) return ;
if ( ! ( await confirmExtractAllStreamsDialog ( ) ) ) return ;
@ -1519,7 +1522,7 @@ const App = memo(() => {
} finally {
setWorking ( ) ;
}
} , [ customOutDir , filePath , mainStreams , outputDir ]) ;
} , [ customOutDir , filePath , mainStreams , outputDir , working ]) ;
const addStreamSourceFile = useCallback ( async ( path ) => {
if ( externalStreamFiles [ path ] ) return ;
@ -1544,7 +1547,6 @@ const App = memo(() => {
projectPath = path ;
path = pathJoin ( dirname ( path ) , mediaFileName ) ;
}
/ / B e c a u s e A p p l e i s b e i n g n a z i a b o u t t h e a b i l i t y t o o p e n " c o p y p r o t e c t e d D V D f i l e s "
const disallowVob = isMasBuild ;
if ( disallowVob && /\.vob$/i . test ( path ) ) {
@ -1576,7 +1578,7 @@ const App = memo(() => {
const userOpenFiles = useCallback ( async ( filePaths ) => {
try {
if ( ! filePaths || filePaths . length < 1 ) return ;
if ( ! filePaths || filePaths . length < 1 || working ) return ;
console . log ( 'userOpenFiles' ) ;
console . log ( filePaths . join ( '\n' ) ) ;
@ -1591,6 +1593,8 @@ const App = memo(() => {
const filePathLowerCase = firstFilePath . toLowerCase ( ) ;
setWorking ( i18n . t ( 'Loading file' ) ) ;
/ / I m p o r t C S V p r o j e c t f o r e x i s t i n g v i d e o
if ( filePathLowerCase . endsWith ( '.csv' ) ) {
if ( ! checkFileOpened ( ) ) return ;
@ -1628,66 +1632,42 @@ const App = memo(() => {
await userOpenSingleFile ( { path : firstFilePath , isLlcProject } ) ;
} catch ( err ) {
console . error ( 'userOpenFiles' , err ) ;
errorToast ( i18n . t ( 'Failed to open file' ) ) ;
if ( err . code === 'LLC_FFPROBE_UNSUPPORTED_FILE' ) {
errorToast ( i18n . t ( 'Unsupported file' ) ) ;
} else {
handleError ( i18n . t ( 'Failed to open file' ) , err ) ;
}
} finally {
setWorking ( ) ;
}
} , [ addStreamSourceFile , checkFileOpened , enableAskForFileOpenAction , isFileOpened , loadEdlFile , mergeFiles , userOpenSingleFile ] ) ;
} , [ addStreamSourceFile , checkFileOpened , enableAskForFileOpenAction , isFileOpened , loadEdlFile , mergeFiles , userOpenSingleFile , working ]) ;
const html5ify = useCallback ( async ( { customOutDir : cod , filePath : fp , speed , hasAudio : ha , hasVideo : hv } ) => {
const path = getHtml5ifiedPath ( cod , fp , speed ) ;
const userHtml5ifyCurrentFile = useCallback ( async ( ) => {
if ( ! filePath || working ) return ;
let audio ;
if ( ha ) {
if ( speed === 'slowest' ) audio = 'hq' ;
else if ( [ 'slow-audio' , 'fastest-audio' ] . includes ( speed ) ) audio = 'lq' ;
else if ( [ 'fast-audio' , 'fastest-audio-remux' ] . includes ( speed ) ) audio = 'copy' ;
}
async function getHtml5ifySpeed ( ) {
const { selectedOption , remember } = await askForHtml5ifySpeed ( { allowedOptions : [ 'fastest' , 'fastest-audio' , 'fastest-audio-remux' , 'fast-audio' , 'fast' , 'slow' , 'slow-audio' , 'slowest' ] , showRemember : true , initialOption : rememberConvertToSupportedFormat } ) ;
if ( ! selectedOption ) return undefined ;
let video ;
if ( hv ) {
if ( speed === 'slowest' ) video = 'hq' ;
else if ( [ 'slow-audio' , 'slow' ] . includes ( speed ) ) video = 'lq' ;
else video = 'copy' ;
}
console . log ( 'Choice' , { speed : selectedOption , remember } ) ;
try {
await ffmpegHtml5ify ( { filePath : fp , outPath : path , video , audio , onProgress : setCutProgress } ) ;
} finally {
setCutProgress ( ) ;
}
return path ;
} , [ ffmpegHtml5ify , getHtml5ifiedPath ] ) ;
setRememberConvertToSupportedFormat ( remember ? selectedOption : undefined ) ;
const html5ifyAndLoad = useCallback ( async ( speed ) => {
if ( [ 'fastest-audio' , 'fastest-audio-remux' ] . includes ( speed ) ) {
const path = await html5ify ( { customOutDir , filePath , speed , hasAudio , hasVideo : false } ) ;
load ( { filePath , dummyVideoPathRequested : path , customOutDir } ) ;
} else {
const path = await html5ify ( { customOutDir , filePath , speed , hasAudio , hasVideo } ) ;
load ( { filePath , html5FriendlyPathRequested : path , customOutDir } ) ;
return selectedOption ;
}
} , [ hasAudio , hasVideo , customOutDir , filePath , html5ify , load ] ) ;
const userHtml5ifyCurrentFile = useCallback ( async ( ) => {
if ( ! filePath ) return ;
try {
setWorking ( i18n . t ( 'Converting to supported format' ) ) ;
const speed = await askForHtml5ifySpeed ( [ 'fastest' , 'fastest-audio' , 'fastest-audio-remux' , 'fast-audio' , 'fast' , 'slow' , 'slow-audio' , 'slowest' ] ) ;
const speed = await getHtml5ifySpeed ( ) ;
if ( ! speed ) return ;
if ( speed === 'fastest' ) {
await createDummyVideo ( customOutDir , filePath ) ;
} else if ( [ 'fastest-audio' , 'fastest-audio-remux' , 'fast-audio' , 'fast' , 'slow' , 'slow-audio' , 'slowest' ] . includes ( speed ) ) {
await html5ifyAndLoad ( speed ) ;
}
setWorking ( i18n . t ( 'Converting to supported format' ) ) ;
await html5ifyAndLoad ( customOutDir , filePath , speed , hasVideo , hasAudio ) ;
} catch ( err ) {
errorToast ( i18n . t ( 'Failed to convert file. Try a different conversion' ) ) ;
console . error ( 'Failed to html5ify file' , err ) ;
} finally {
setWorking ( ) ;
}
} , [ c reateDummyVideo, c ustomOutDir, filePath , html5ifyAndLoad ] ) ;
} , [ c ustomOutDir, filePath , html5ifyAndLoad , hasVideo , hasAudio , rememberConvertToSupportedFormat , working ] ) ;
const onVideoError = useCallback ( async ( ) => {
const { error } = videoRef . current ;
@ -1696,25 +1676,35 @@ const App = memo(() => {
console . error ( 'onVideoError' , error . message , error . code ) ;
function showToast ( ) {
console . log ( 'Trying to create dummy' ) ;
if ( ! hideAllNotifications ) toast . fire ( { icon : 'info' , text : 'This file is not natively supported. Creating a preview file...' } ) ;
}
const PIPELINE _ERROR _DECODE = 3 ; / / T o r e p r o d u c e : " R X 1 0 0 V I I P C M a u d i o t i m e c o d e . M P 4 " o r s e e h t t p s : / / g i t h u b . c o m / m i f i / l o s s l e s s - c u t / i s s u e s / 8 0 4
const MEDIA _ERR _SRC _NOT _SUPPORTED = 4 ;
if ( [ MEDIA _ERR _SRC _NOT _SUPPORTED , PIPELINE _ERROR _DECODE ] . includes ( error . code ) && ! usingDummyVideo ) {
if ( hasVideo ) {
try {
const PIPELINE _ERROR _DECODE = 3 ; / / T o r e p r o d u c e : " R X 1 0 0 V I I P C M a u d i o t i m e c o d e . M P 4 " o r s e e h t t p s : / / g i t h u b . c o m / m i f i / l o s s l e s s - c u t / i s s u e s / 8 0 4
const MEDIA _ERR _SRC _NOT _SUPPORTED = 4 ; / / T e s t : i s s u e - 6 6 8 - 3 . 2 0 . 1 . m 2 t s - N O T E : D E M U X E R _ E R R O R _ C O U L D _ N O T _ O P E N i s a l s o 4
if ( [ MEDIA _ERR _SRC _NOT _SUPPORTED , PIPELINE _ERROR _DECODE ] . includes ( error . code ) && ! usingPreviewFile && filePath && ! working ) {
if ( isDurationValid ( await getDuration ( filePath ) ) ) {
showToast ( ) ;
await tryCreateDummyVideo ( ) ;
console . log ( 'Trying to create preview' ) ;
if ( ! hideAllNotifications ) toast . fire ( { icon : 'info' , text : 'This file is not natively supported. Creating a preview file...' } ) ;
try {
setWorking ( i18n . t ( 'Converting to supported format' ) ) ;
if ( hasVideo ) {
/ / " f a s t e s t " i s t h e m o s t l i k e l y t y p e n o t t o f a i l f o r v i d e o ( b u t i t i s m u t e d ) .
await html5ifyAndLoad ( customOutDir , filePath , rememberConvertToSupportedFormat || 'fastest' , hasVideo , hasAudio ) ;
} else if ( hasAudio ) {
/ / F o r a u d i o d o a f a s t r e - e n c o d e
await html5ifyAndLoad ( customOutDir , filePath , rememberConvertToSupportedFormat || 'fastest-audio' , hasVideo , hasAudio ) ;
}
} catch ( err ) {
console . error ( err ) ;
showPlaybackFailedMessage ( ) ;
} finally {
setWorking ( ) ;
}
}
} else if ( hasAudio ) {
showToast ( ) ;
await html5ifyAndLoad ( 'fastest-audio' ) ;
}
} catch ( err ) {
handleError ( err ) ;
}
} , [ tryCreateDummyVideo , fileUri , usingDummyVideo , hasVideo , hasAudio , html5ifyAndLoad , hideAllNotifications , filePath ] ) ;
} , [ fileUri, usingPreviewFile , hasVideo , hasAudio , html5ifyAndLoad , hideAllNotifications , customOutDir, filePath, working , rememberConvertToSupportedFormat ] ) ;
useEffect ( ( ) => {
function showOpenAndMergeDialog2 ( ) {
@ -1762,6 +1752,7 @@ const App = memo(() => {
}
async function batchConvertFriendlyFormat ( ) {
if ( working ) return ;
const title = i18n . t ( 'Select files to batch convert to supported format' ) ;
const { canceled , filePaths } = await dialog . showOpenDialog ( { properties : [ 'openFile' , 'multiSelections' ] , title , message : title } ) ;
if ( canceled || filePaths . length < 1 ) return ;
@ -1769,7 +1760,7 @@ const App = memo(() => {
const failedFiles = [ ] ;
let i = 0 ;
const speed = await askForHtml5ifySpeed ( [ 'fastest-audio' , 'fastest-audio-remux' , 'fast-audio' , 'fast' , 'slow' , 'slow-audio' , 'slowest' ] ) ;
const { selectedOption : speed } = await askForHtml5ifySpeed ( { allowedOptions : [ 'fastest-audio' , 'fastest-audio-remux' , 'fast-audio' , 'fast' , 'slow' , 'slow-audio' , 'slowest' ] } ) ;
if ( ! speed ) return ;
try {
@ -1820,12 +1811,12 @@ const App = memo(() => {
}
async function fixInvalidDuration2 ( ) {
if ( ! checkFileOpened ( ) ) return ;
if ( ! checkFileOpened ( ) || working ) return ;
try {
setWorking ( i18n . t ( 'Fixing file duration' ) ) ;
const path = await fixInvalidDuration ( { fileFormat , customOutDir } ) ;
load ( { filePath : path , customOutDir } ) ;
toast . fire ( { icon : 'info' , text : i18n . t ( 'Duration has been fixed' ) } ) ;
await load ( { filePath : path , customOutDir } ) ;
} catch ( err ) {
errorToast ( i18n . t ( 'Failed to fix file duration' ) ) ;
console . error ( 'Failed to fix file duration' , err ) ;
@ -1886,8 +1877,8 @@ const App = memo(() => {
} ;
} , [
mergeFiles , outputDir , filePath , customOutDir , startTimeOffset , userHtml5ifyCurrentFile ,
createDummyVideo, extractAllStreams, userOpenFiles , openSendReportDialogWithState ,
loadEdlFile , cutSegments , apparentCutSegments , edlFilePath , toggleHelp , toggleSettings , assureOutDirAccess , html5ifyAndLoad , html5ify,
extractAllStreams, userOpenFiles , openSendReportDialogWithState ,
loadEdlFile , cutSegments , apparentCutSegments , edlFilePath , toggleHelp , toggleSettings , assureOutDirAccess , html5ifyAndLoad , working, html5ify,
loadCutSegments , duration , checkFileOpened , load , fileFormat , reorderSegsByStartTime , closeFile , clearSegments , fixInvalidDuration , invertAllCutSegments ,
] ) ;