diff --git a/package.json b/package.json index 6ccfc5e1..5069b415 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,6 @@ "json5": "^2.2.0", "lodash": "^4.17.19", "mime-types": "^2.1.14", - "open": "^7.0.3", "semver": "^7.1.3", "string-to-stream": "^1.1.1", "strtok3": "^6.0.0", diff --git a/src/App.jsx b/src/App.jsx index ff2c84ce..c6641362 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1115,7 +1115,7 @@ const App = memo(() => { const metadataFromPath = paths[0]; await concatFiles({ paths, outPath, outDir, outFormat, metadataFromPath, includeAllStreams, streams, ffmpegExperimental, onProgress: setCutProgress, preserveMovData, movFastStart, preserveMetadataOnMerge, chapters: chaptersFromSegments, appendFfmpegCommandLog }); if (clearBatchFilesAfterConcat) closeBatch(); - openDirToast({ icon: 'success', dirPath: outDir, text: i18n.t('Files merged!') }); + openDirToast({ icon: 'success', filePath: outPath, text: i18n.t('Files merged!') }); } catch (err) { if (isOutOfSpaceError(err)) { showDiskFull(); @@ -1325,13 +1325,14 @@ const App = memo(() => { enableOverwriteOutput, }); + let concatOutPath; if (willMerge) { setCutProgress(0); setWorking(i18n.t('Merging')); const chapterNames = segmentsToChapters && !invertCutSegments ? segmentsToExport.map((s) => s.name) : undefined; - await autoConcatCutSegments({ + concatOutPath = await autoConcatCutSegments({ customOutDir, outFormat: fileFormat, isCustomFormatSelected, @@ -1361,7 +1362,8 @@ const App = memo(() => { } } - if (!hideAllNotifications) openDirToast({ dirPath: outputDir, text: msgs.join(' '), timer: 15000 }); + const revealPath = concatOutPath || outFiles[0]; + if (!hideAllNotifications) openDirToast({ filePath: revealPath, text: msgs.join(' '), timer: 15000 }); } catch (err) { if (err instanceof RefuseOverwriteError) { showRefuseToOverwrite(); @@ -1409,12 +1411,12 @@ const App = memo(() => { ? await captureFramesFfmpeg({ customOutDir, filePath, fromTime: currentTime, captureFormat, enableTransferTimestamps, numFrames: 1 }) : await captureFrameFromTag({ customOutDir, filePath, currentTime, captureFormat, video, enableTransferTimestamps }); - if (!hideAllNotifications) openDirToast({ dirPath: outputDir, text: `${i18n.t('Screenshot captured to:')} ${outPath}` }); + if (!hideAllNotifications) openDirToast({ filePath: outPath, text: `${i18n.t('Screenshot captured to:')} ${outPath}` }); } catch (err) { console.error(err); errorToast(i18n.t('Failed to capture frame')); } - }, [filePath, getCurrentTime, usingPreviewFile, customOutDir, captureFormat, enableTransferTimestamps, hideAllNotifications, outputDir]); + }, [filePath, getCurrentTime, usingPreviewFile, customOutDir, captureFormat, enableTransferTimestamps, hideAllNotifications]); const extractSegmentFramesAsImages = useCallback(async (index) => { if (!filePath) return; @@ -1425,8 +1427,8 @@ const App = memo(() => { try { setWorking(i18n.t('Extracting frames')); - await captureFramesFfmpeg({ customOutDir, filePath, fromTime: start, captureFormat, enableTransferTimestamps, numFrames }); - if (!hideAllNotifications) openDirToast({ dirPath: outputDir, text: i18n.t('Frames extracted to: {{path}}', { path: outputDir }) }); + const outPath = await captureFramesFfmpeg({ customOutDir, filePath, fromTime: start, captureFormat, enableTransferTimestamps, numFrames }); + if (!hideAllNotifications) openDirToast({ filePath: outPath, text: i18n.t('Frames extracted to: {{path}}', { path: outputDir }) }); } catch (err) { handleError(err); } finally { @@ -1742,8 +1744,8 @@ const App = memo(() => { try { setWorking(i18n.t('Extracting all streams')); setStreamsSelectorShown(false); - await extractStreams({ customOutDir, filePath, streams: mainStreams, enableOverwriteOutput }); - openDirToast({ dirPath: outputDir, text: i18n.t('All streams have been extracted as separate files') }); + const extractedPaths = await extractStreams({ customOutDir, filePath, streams: mainStreams, enableOverwriteOutput }); + openDirToast({ filePath: extractedPaths[0], text: i18n.t('All streams have been extracted as separate files') }); } catch (err) { if (err instanceof RefuseOverwriteError) { showRefuseToOverwrite(); @@ -1754,7 +1756,7 @@ const App = memo(() => { } finally { setWorking(); } - }, [customOutDir, enableOverwriteOutput, filePath, mainStreams, outputDir, setWorking]); + }, [customOutDir, enableOverwriteOutput, filePath, mainStreams, setWorking]); const detectBlackScenes = useCallback(async () => { if (!filePath) return; @@ -2161,8 +2163,8 @@ const App = memo(() => { try { setWorking(i18n.t('Extracting track')); // setStreamsSelectorShown(false); - await extractStreams({ customOutDir, filePath, streams: mainStreams.filter((s) => s.index === index), enableOverwriteOutput }); - openDirToast({ dirPath: outputDir, text: i18n.t('Track has been extracted') }); + const extractedPaths = await extractStreams({ customOutDir, filePath, streams: mainStreams.filter((s) => s.index === index), enableOverwriteOutput }); + openDirToast({ filePath: extractedPaths[0], text: i18n.t('Track has been extracted') }); } catch (err) { if (err instanceof RefuseOverwriteError) { showRefuseToOverwrite(); @@ -2173,7 +2175,7 @@ const App = memo(() => { } finally { setWorking(); } - }, [customOutDir, enableOverwriteOutput, filePath, mainStreams, outputDir, setWorking]); + }, [customOutDir, enableOverwriteOutput, filePath, mainStreams, setWorking]); const batchFilePaths = useMemo(() => batchFiles.map((f) => f.path), [batchFiles]); diff --git a/src/ffmpeg.js b/src/ffmpeg.js index 475d6d4b..45bd10fe 100644 --- a/src/ffmpeg.js +++ b/src/ffmpeg.js @@ -338,12 +338,12 @@ function getPreferredCodecFormat({ codec_name: codec, codec_type: type }) { } async function extractNonAttachmentStreams({ customOutDir, filePath, streams, enableOverwriteOutput }) { - if (streams.length === 0) return; + if (streams.length === 0) return []; console.log('Extracting', streams.length, 'normal streams'); let streamArgs = []; - await pMap(streams, async ({ index, codec, type, format: { format, ext } }) => { + const outPaths = await pMap(streams, async ({ index, codec, type, format: { format, ext } }) => { const outPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix: `stream-${index}-${type}-${codec}.${ext}` }); if (!enableOverwriteOutput && await pathExists(outPath)) throw new RefuseOverwriteError(); @@ -351,6 +351,7 @@ async function extractNonAttachmentStreams({ customOutDir, filePath, streams, en ...streamArgs, '-map', `0:${index}`, '-c', 'copy', '-f', format, '-y', outPath, ]; + return outPath; }, { concurrency: 1 }); const ffmpegArgs = [ @@ -362,15 +363,17 @@ async function extractNonAttachmentStreams({ customOutDir, filePath, streams, en const { stdout } = await runFfmpeg(ffmpegArgs); console.log(stdout); + + return outPaths; } async function extractAttachmentStreams({ customOutDir, filePath, streams, enableOverwriteOutput }) { - if (streams.length === 0) return; + if (streams.length === 0) return []; console.log('Extracting', streams.length, 'attachment streams'); let streamArgs = []; - await pMap(streams, async ({ index, codec_name: codec, codec_type: type }) => { + const outPaths = await pMap(streams, async ({ index, codec_name: codec, codec_type: type }) => { const ext = codec || 'bin'; const outPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix: `stream-${index}-${type}-${codec}.${ext}` }); if (!enableOverwriteOutput && await pathExists(outPath)) throw new RefuseOverwriteError(); @@ -379,6 +382,7 @@ async function extractAttachmentStreams({ customOutDir, filePath, streams, enabl ...streamArgs, `-dump_attachment:${index}`, outPath, ]; + return outPath; }, { concurrency: 1 }); const ffmpegArgs = [ @@ -395,9 +399,10 @@ async function extractAttachmentStreams({ customOutDir, filePath, streams, enabl } catch (err) { // Unfortunately ffmpeg will exit with code 1 even though it's a success // Note: This is kind of hacky: - if (err.exitCode === 1 && typeof err.stderr === 'string' && err.stderr.includes('At least one output file must be specified')) return; + if (err.exitCode === 1 && typeof err.stderr === 'string' && err.stderr.includes('At least one output file must be specified')) return outPaths; throw err; } + return outPaths; } // https://stackoverflow.com/questions/32922226/extract-every-audio-and-subtitles-from-a-video-with-ffmpeg @@ -418,8 +423,10 @@ export async function extractStreams({ filePath, customOutDir, streams, enableOv // TODO progress // Attachment streams are handled differently from normal streams - await extractNonAttachmentStreams({ customOutDir, filePath, streams: outStreams, enableOverwriteOutput }); - await extractAttachmentStreams({ customOutDir, filePath, streams: attachmentStreams, enableOverwriteOutput }); + return [ + ...(await extractNonAttachmentStreams({ customOutDir, filePath, streams: outStreams, enableOverwriteOutput })), + ...(await extractAttachmentStreams({ customOutDir, filePath, streams: attachmentStreams, enableOverwriteOutput })), + ]; } async function renderThumbnail(filePath, timestamp) { diff --git a/src/hooks/useFfmpegOperations.js b/src/hooks/useFfmpegOperations.js index d3298bca..e4396736 100644 --- a/src/hooks/useFfmpegOperations.js +++ b/src/hooks/useFfmpegOperations.js @@ -453,6 +453,8 @@ function useFfmpegOperations({ filePath, enableTransferTimestamps }) { const { streams } = await readFileMeta(metadataFromPath); await concatFiles({ paths: segmentPaths, outDir, outPath, metadataFromPath, outFormat, includeAllStreams: true, streams, ffmpegExperimental, onProgress, preserveMovData, movFastStart, chapters, preserveMetadataOnMerge, appendFfmpegCommandLog }); if (autoDeleteMergedSegments) await tryDeleteFiles(segmentPaths); + + return outPath; }, [concatFiles, filePath]); const html5ify = useCallback(async ({ customOutDir, filePath: filePathArg, speed, hasAudio, hasVideo, onProgress }) => { diff --git a/src/util.js b/src/util.js index e187a273..bbbba28c 100644 --- a/src/util.js +++ b/src/util.js @@ -5,7 +5,6 @@ import pMap from 'p-map'; const { dirname, parse: parsePath, join, basename, extname, isAbsolute, resolve } = window.require('path'); const fs = window.require('fs-extra'); -const open = window.require('open'); const os = window.require('os'); const { shell } = window.require('electron'); @@ -128,9 +127,9 @@ export function handleError(arg1, arg2) { } -export const openDirToast = async ({ dirPath, ...props }) => { +export const openDirToast = async ({ filePath, ...props }) => { const { value } = await toast.fire({ icon: 'success', timer: 5000, showConfirmButton: true, confirmButtonText: i18n.t('Show'), showCancelButton: true, cancelButtonText: i18n.t('Close'), ...props }); - if (value) open(dirPath); + if (value) shell.showItemInFolder(filePath); }; export function setFileNameTitle(filePath) { diff --git a/yarn.lock b/yarn.lock index c0ea74a5..244056bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10694,7 +10694,6 @@ __metadata: mkdirp: ^1.0.3 moment: ^2.29.4 mousetrap: ^1.6.5 - open: ^7.0.3 p-map: ^4.0.0 patch-package: ^6.2.1 pify: ^5.0.0 @@ -11714,7 +11713,7 @@ __metadata: languageName: node linkType: hard -"open@npm:^7.0.3, open@npm:^7.4.2": +"open@npm:^7.4.2": version: 7.4.2 resolution: "open@npm:7.4.2" dependencies: