2022-06-29 23:22:14 +00:00
|
|
|
import { getRealVideoStreams, getVideoTimebase } from './util/streams';
|
2022-02-28 07:07:51 +00:00
|
|
|
|
|
|
|
import { readFrames } from './ffmpeg';
|
|
|
|
|
|
|
|
const { stat } = window.require('fs-extra');
|
|
|
|
|
|
|
|
function mapInputToOutputCodec(inputCodec) {
|
|
|
|
// if (inputCodec === 'hevc') return 'libx265';
|
|
|
|
return inputCodec;
|
|
|
|
}
|
|
|
|
|
|
|
|
// eslint-disable-next-line import/prefer-default-export
|
|
|
|
export async function getSmartCutParams({ path, videoDuration, desiredCutFrom, streams }) {
|
|
|
|
const videoStreams = getRealVideoStreams(streams);
|
|
|
|
if (videoStreams.length > 1) throw new Error('Can only smart cut video with exactly one video stream');
|
|
|
|
|
|
|
|
const videoStream = videoStreams[0];
|
|
|
|
|
|
|
|
async function readKeyframes(window) {
|
|
|
|
const frames = await readFrames({ filePath: path, aroundTime: desiredCutFrom, streamIndex: videoStream.index, window });
|
2022-06-15 18:56:19 +00:00
|
|
|
return frames.filter((frame) => frame.keyframe);
|
2022-02-28 07:07:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let keyframes = await readKeyframes(10);
|
|
|
|
|
|
|
|
const keyframeAtExactTime = keyframes.find((keyframe) => Math.abs(keyframe.time - desiredCutFrom) < 0.000001);
|
|
|
|
if (keyframeAtExactTime) {
|
|
|
|
console.log('Start cut is already on exact keyframe', keyframeAtExactTime.time);
|
|
|
|
|
|
|
|
return {
|
|
|
|
cutFrom: keyframeAtExactTime.time,
|
|
|
|
videoStreamIndex: videoStream.index,
|
|
|
|
needsSmartCut: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const findNextKeyframe = () => keyframes.find((keyframe) => keyframe.time > desiredCutFrom); // (they are already sorted)
|
|
|
|
let nextKeyframe = findNextKeyframe();
|
|
|
|
if (!nextKeyframe) {
|
|
|
|
console.log('Cannot find any keyframe after desired start cut point, trying with larger window');
|
|
|
|
keyframes = await readKeyframes(60);
|
|
|
|
nextKeyframe = findNextKeyframe();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!nextKeyframe) throw new Error('Cannot find any keyframe after desired start cut point');
|
|
|
|
|
|
|
|
console.log('Smart cut from keyframe', { keyframe: nextKeyframe.time, desiredCutFrom });
|
|
|
|
|
|
|
|
let videoBitrate = parseInt(videoStream.bit_rate, 10);
|
|
|
|
if (Number.isNaN(videoBitrate)) {
|
|
|
|
console.warn('Unable to detect input bitrate');
|
|
|
|
const stats = await stat(path);
|
|
|
|
videoBitrate = stats.size / videoDuration;
|
|
|
|
}
|
|
|
|
|
|
|
|
const videoCodec = mapInputToOutputCodec(videoStream.codec_name);
|
|
|
|
if (videoCodec == null) throw new Error('Unable to determine codec for smart cut');
|
|
|
|
|
2022-06-29 23:22:14 +00:00
|
|
|
const timebase = getVideoTimebase(videoStream);
|
2022-02-28 07:07:51 +00:00
|
|
|
if (timebase == null) console.warn('Unable to determine timebase', videoStream.time_base);
|
|
|
|
|
|
|
|
return {
|
|
|
|
cutFrom: nextKeyframe.time,
|
|
|
|
videoStreamIndex: videoStream.index,
|
|
|
|
needsSmartCut: true,
|
|
|
|
videoCodec,
|
|
|
|
videoBitrate: Math.floor(videoBitrate),
|
|
|
|
videoTimebase: timebase,
|
|
|
|
};
|
|
|
|
}
|