improve ffmpeg logging

pull/2263/head
Mikael Finstad 11 months ago
parent 1ea7adc3ca
commit 8b7375b44f
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26

@ -103,7 +103,8 @@ function getExecaOptions({ env, ...customExecaOptions }: Omit<ExecaOptions<Buffe
// todo collect warnings from ffmpeg output and show them after export? example: https://github.com/mifi/lossless-cut/issues/1469
function runFfmpegProcess(args: readonly string[], customExecaOptions?: Omit<ExecaOptions<BufferEncodingOption>, '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<Buffer> | undefined;
let ps2: ExecaChildProcess<Buffer> | 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);
}

@ -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 });

@ -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');
}

@ -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,
};
}

@ -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,

Loading…
Cancel
Save