kopia lustrzana https://github.com/mifi/lossless-cut
rodzic
df5c6e5c8a
commit
65a0b9e138
|
@ -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",
|
||||
|
|
28
src/App.jsx
28
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]);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
Ładowanie…
Reference in New Issue