diff --git a/src/main/ffmpeg.ts b/src/main/ffmpeg.ts index 59e80b95..1223266e 100644 --- a/src/main/ffmpeg.ts +++ b/src/main/ffmpeg.ts @@ -103,7 +103,8 @@ function getExecaOptions({ env, ...customExecaOptions }: Omit, 'encoding'>, additionalOptions?: { logCli?: boolean }) { const ffmpegPath = getFfmpegPath(); - if (additionalOptions?.logCli) logger.info(getFfCommandLine('ffmpeg', args)); + const { logCli = true } = additionalOptions ?? {}; + if (logCli) logger.info(getFfCommandLine('ffmpeg', args)); const process = execa(ffmpegPath, args, getExecaOptions(customExecaOptions)); @@ -138,16 +139,15 @@ export async function runFfmpegWithProgress({ ffmpegArgs, duration, onProgress } duration?: number | undefined, onProgress: (a: number) => void, }) { - logger.info(getFfCommandLine('ffmpeg', ffmpegArgs)); const process = runFfmpegProcess(ffmpegArgs); assert(process.stderr != null); handleProgress(process, duration, onProgress); return process; } -export async function runFfprobe(args: readonly string[], { timeout = isDev ? 10000 : 30000 } = {}) { +export async function runFfprobe(args: readonly string[], { timeout = isDev ? 10000 : 30000, logCli = true } = {}) { const ffprobePath = getFfprobePath(); - logger.info(getFfCommandLine('ffprobe', args)); + if (logCli) logger.info(getFfCommandLine('ffprobe', args)); const ps = execa(ffprobePath, args, getExecaOptions()); const timer = setTimeout(() => { logger.warn('killing timed out ffprobe'); @@ -185,8 +185,7 @@ export async function renderWaveformPng({ filePath, start, duration, color, stre '-', ]; - logger.info(getFfCommandLine('ffmpeg1', args1)); - logger.info('|', getFfCommandLine('ffmpeg2', args2)); + logger.info(`${getFfCommandLine('ffmpeg1', args1)} | \n${getFfCommandLine('ffmpeg2', args2)}`); let ps1: ExecaChildProcess | undefined; let ps2: ExecaChildProcess | undefined; @@ -259,7 +258,6 @@ export async function detectSceneChanges({ filePath, minChange, onProgress, from '-filter_complex', `select='gt(scene,${minChange})',metadata=print:file=-`, '-f', 'null', '-', ]; - logger.info(getFfCommandLine('ffmpeg', args)); const process = runFfmpegProcess(args, { buffer: false }); const times = [0]; @@ -293,7 +291,6 @@ async function detectIntervals({ filePath, customArgs, onProgress, from, to, mat ...customArgs, '-f', 'null', '-', ]; - logger.info(getFfCommandLine('ffmpeg', args)); const process = runFfmpegProcess(args, { buffer: false }); let segments: { start: number, end: number }[] = []; @@ -438,12 +435,12 @@ export async function captureFrames({ from, to, videoPath, outPathTemplate, qual '-y', outPathTemplate, ]; - logger.info(getFfCommandLine('ffmpeg', args)); const process = runFfmpegProcess(args, { buffer: false }); handleProgress(process, to - from, onProgress); await process; + return args; } export async function captureFrame({ timestamp, videoPath, outPath, quality }: { @@ -457,8 +454,8 @@ export async function captureFrame({ timestamp, videoPath, outPath, quality }: { '-q:v', String(ffmpegQuality), '-y', outPath, ]; - logger.info(getFfCommandLine('ffmpeg', args)); await runFfmpegProcess(args); + return args; } @@ -577,7 +574,6 @@ export async function html5ify({ outPath, filePath: filePathArg, speed, hasAudio ]; const duration = await getDuration(filePathArg); - logger.info(getFfCommandLine('ffmpeg', ffmpegArgs)); const process = runFfmpegProcess(ffmpegArgs); if (duration) handleProgress(process, duration, onProgress); @@ -605,7 +601,7 @@ export function readOneJpegFrame({ path, seekTo, videoStreamIndex }: { path: str ]; // logger.info(getFfCommandLine('ffmpeg', args)); - return runFfmpegProcess(args, undefined, { logCli: true }); + return runFfmpegProcess(args, undefined, { logCli: false }); } const enableLog = false; @@ -684,7 +680,6 @@ export async function downloadMediaUrl(url: string, outPath: string) { outPath, ]; - logger.info(getFfCommandLine('ffmpeg', args)); await runFfmpegProcess(args); } diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index ebedba17..80f019c9 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -374,8 +374,6 @@ function App() { const jumpTimelineStart = useCallback(() => seekAbs(0), [seekAbs]); const jumpTimelineEnd = useCallback(() => seekAbs(durationSafe), [durationSafe, seekAbs]); - const { captureFrameFromTag, captureFrameFromFfmpeg, captureFramesRange } = useFrameCapture({ formatTimecode, treatOutputFileModifiedTimeAsStart }); - // const getSafeCutTime = useCallback((cutTime, next) => ffmpeg.getSafeCutTime(neighbouringFrames, cutTime, next), [neighbouringFrames]); const outputDir = getOutDir(customOutDir, filePath); @@ -596,9 +594,11 @@ function App() { const needSmartCut = !!(areWeCutting && enableSmartCut); const { - concatFiles, html5ifyDummy, cutMultiple, autoConcatCutSegments, html5ify, fixInvalidDuration, + appendFfmpegCommandLog, concatFiles, html5ifyDummy, cutMultiple, autoConcatCutSegments, html5ify, fixInvalidDuration, } = useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, treatOutputFileModifiedTimeAsStart, needSmartCut, enableOverwriteOutput, outputPlaybackRate, cutFromAdjustmentFrames, appendLastCommandsLog, smartCutCustomBitrate: smartCutBitrate }); + const { captureFrameFromTag, captureFrameFromFfmpeg, captureFramesRange } = useFrameCapture({ appendFfmpegCommandLog, formatTimecode, treatOutputFileModifiedTimeAsStart }); + const html5ifyAndLoad = useCallback(async (cod: string | undefined, fp: string, speed: Html5ifyMode, hv: boolean, ha: boolean) => { const usesDummyVideo = speed === 'fastest'; console.log('html5ifyAndLoad', { speed, hasVideo: hv, hasAudio: ha, usesDummyVideo }); diff --git a/src/renderer/src/ffmpeg.ts b/src/renderer/src/ffmpeg.ts index 640b2658..0f6bec8f 100644 --- a/src/renderer/src/ffmpeg.ts +++ b/src/renderer/src/ffmpeg.ts @@ -79,7 +79,7 @@ export async function readFrames({ filePath, from, to, streamIndex }: { filePath: string, from?: number | undefined, to?: number | undefined, streamIndex: number, }) { const intervalsArgs = from != null && to != null ? ['-read_intervals', `${from}%${to}`] : []; - const { stdout } = await runFfprobe(['-v', 'error', ...intervalsArgs, '-show_packets', '-select_streams', String(streamIndex), '-show_entries', 'packet=pts_time,flags', '-of', 'json', filePath]); + const { stdout } = await runFfprobe(['-v', 'error', ...intervalsArgs, '-show_packets', '-select_streams', String(streamIndex), '-show_entries', 'packet=pts_time,flags', '-of', 'json', filePath], { logCli: false }); const packetsFiltered: Frame[] = (JSON.parse(stdout as unknown as string).packets as { flags: string, pts_time: string }[]) .map((p) => ({ keyframe: p.flags[0] === 'K', @@ -443,7 +443,7 @@ async function extractNonAttachmentStreams({ customOutDir, filePath, streams, en ...streamArgs, ]; - const { stdout } = await runFfmpeg(ffmpegArgs, undefined, { logCli: true }); + const { stdout } = await runFfmpeg(ffmpegArgs); console.log(stdout.toString('utf8')); return outPaths; @@ -479,7 +479,7 @@ async function extractAttachmentStreams({ customOutDir, filePath, streams, enabl ]; try { - const { stdout } = await runFfmpeg(ffmpegArgs, undefined, { logCli: true }); + const { stdout } = await runFfmpeg(ffmpegArgs); console.log(stdout.toString('utf8')); } catch (err) { // Unfortunately ffmpeg will exit with code 1 even though it's a success @@ -517,7 +517,7 @@ async function renderThumbnail(filePath: string, timestamp: number, signal: Abor '-', ]; - const { stdout } = await runFfmpeg(args, { signal }); + const { stdout } = await runFfmpeg(args, { signal }, { logCli: false }); const blob = new Blob([fixRemoteBuffer(stdout)], { type: 'image/jpeg' }); return URL.createObjectURL(blob); @@ -601,7 +601,7 @@ export async function extractWaveform({ filePath, outPath }: { filePath: string, '-f', 'wav', '-y', outPath, - ]); + ], undefined, { logCli: false }); console.timeEnd('ffmpeg'); } diff --git a/src/renderer/src/hooks/useFfmpegOperations.ts b/src/renderer/src/hooks/useFfmpegOperations.ts index 7322cb7f..7434ac8a 100644 --- a/src/renderer/src/hooks/useFfmpegOperations.ts +++ b/src/renderer/src/hooks/useFfmpegOperations.ts @@ -675,7 +675,7 @@ function useFfmpegOperations({ filePath, treatInputFileModifiedTimeAsStart, trea }, [appendFfmpegCommandLog, filePath, treatOutputFileModifiedTimeAsStart]); return { - cutMultiple, concatFiles, html5ify, html5ifyDummy, fixInvalidDuration, autoConcatCutSegments, + appendFfmpegCommandLog, cutMultiple, concatFiles, html5ify, html5ifyDummy, fixInvalidDuration, autoConcatCutSegments, }; } diff --git a/src/renderer/src/hooks/useFrameCapture.ts b/src/renderer/src/hooks/useFrameCapture.ts index 0e4b552c..7e2bfa16 100644 --- a/src/renderer/src/hooks/useFrameCapture.ts +++ b/src/renderer/src/hooks/useFrameCapture.ts @@ -5,7 +5,7 @@ import { useCallback } from 'react'; import { getSuffixedOutPath, getOutDir, transferTimestamps, getSuffixedFileName, getOutPath, escapeRegExp, fsOperationWithRetry } from '../util'; import { getNumDigits } from '../segments'; -import { captureFrame as ffmpegCaptureFrame, captureFrames as ffmpegCaptureFrames } from '../ffmpeg'; +import * as ffmpeg from '../ffmpeg'; import { FormatTimecode } from '../types'; import { CaptureFormat } from '../../../../types'; @@ -25,7 +25,8 @@ function getFrameFromVideo(video: HTMLVideoElement, format: CaptureFormat, quali return dataUriToBuffer(dataUri); } -export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }: { +export default ({ appendFfmpegCommandLog, formatTimecode, treatOutputFileModifiedTimeAsStart }: { + appendFfmpegCommandLog: (args: string[]) => void, formatTimecode: FormatTimecode, treatOutputFileModifiedTimeAsStart?: boolean | undefined | null, }) => { @@ -51,7 +52,8 @@ export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }: { const outPathTemplate = getSuffixedOutPath({ customOutDir, filePath, nameSuffix: nameTemplateSuffix }); const firstFileOutPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix }); - await ffmpegCaptureFrames({ from: fromTime, to: toTime, videoPath: filePath, outPathTemplate, captureFormat, quality, filter, onProgress }); + const args = await ffmpeg.captureFrames({ from: fromTime, to: toTime, videoPath: filePath, outPathTemplate, captureFormat, quality, filter, onProgress }); + appendFfmpegCommandLog(args); return firstFileOutPath; } @@ -59,7 +61,8 @@ export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }: { // see https://github.com/mifi/lossless-cut/issues/1139 const tmpSuffix = 'llc-tmp-frame-capture-'; const outPathTemplate = getSuffixedOutPath({ customOutDir, filePath, nameSuffix: getSuffix(`${tmpSuffix}%d`) }); - await ffmpegCaptureFrames({ from: fromTime, to: toTime, videoPath: filePath, outPathTemplate, captureFormat, quality, filter, framePts: true, onProgress }); + const args = await ffmpeg.captureFrames({ from: fromTime, to: toTime, videoPath: filePath, outPathTemplate, captureFormat, quality, filter, framePts: true, onProgress }); + appendFfmpegCommandLog(args); const outDir = getOutDir(customOutDir, filePath); const files = await readdir(outDir); @@ -85,7 +88,7 @@ export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }: { }, { concurrency: 1 }); return outPaths[0]; - }, [formatTimecode]); + }, [appendFfmpegCommandLog, formatTimecode]); const captureFrameFromFfmpeg = useCallback(async ({ customOutDir, filePath, fromTime, captureFormat, quality }: { customOutDir?: string | undefined, filePath: string, fromTime: number, captureFormat: CaptureFormat, quality: number, @@ -93,11 +96,12 @@ export default ({ formatTimecode, treatOutputFileModifiedTimeAsStart }: { const time = formatTimecode({ seconds: fromTime, fileNameFriendly: true }); const nameSuffix = `${time}.${captureFormat}`; const outPath = getSuffixedOutPath({ customOutDir, filePath, nameSuffix }); - await ffmpegCaptureFrame({ timestamp: fromTime, videoPath: filePath, outPath, quality }); + const args = await ffmpeg.captureFrame({ timestamp: fromTime, videoPath: filePath, outPath, quality }); + appendFfmpegCommandLog(args); await transferTimestamps({ inPath: filePath, outPath, cutFrom: fromTime, treatOutputFileModifiedTimeAsStart }); return outPath; - }, [formatTimecode, treatOutputFileModifiedTimeAsStart]); + }, [appendFfmpegCommandLog, formatTimecode, treatOutputFileModifiedTimeAsStart]); const captureFrameFromTag = useCallback(async ({ customOutDir, filePath, currentTime, captureFormat, video, quality }: { customOutDir?: string | undefined, filePath: string, currentTime: number, captureFormat: CaptureFormat, video: HTMLVideoElement, quality: number,