kopia lustrzana https://github.com/mifi/lossless-cut
support for grabbing a frame at 4k/hevc to jpg even though the preview is not native #88
rodzic
bde25d2265
commit
d683fbbb69
15
src/App.jsx
15
src/App.jsx
|
@ -35,7 +35,7 @@ import { loadMifiLink } from './mifi';
|
|||
import { primaryColor, controlsBackground, waveformColor } from './colors';
|
||||
import { showMergeDialog, showOpenAndMergeDialog } from './merge/merge';
|
||||
import allOutFormats from './outFormats';
|
||||
import captureFrame from './capture-frame';
|
||||
import { captureFrameFromTag, captureFrameFfmpeg } from './capture-frame';
|
||||
import {
|
||||
defaultProcessedCodecTypes, getStreamFps, isCuttingStart, isCuttingEnd,
|
||||
getDefaultOutFormat, getFormatData, renderFrame, mergeAnyFiles, renderThumbnails as ffmpegRenderThumbnails,
|
||||
|
@ -920,15 +920,16 @@ const App = memo(() => {
|
|||
exportExtraStreams, nonCopiedExtraStreams, outputDir, shortestFlag,
|
||||
]);
|
||||
|
||||
// TODO use ffmpeg to capture frame
|
||||
const capture = useCallback(async () => {
|
||||
if (!filePath) return;
|
||||
if (html5FriendlyPath || dummyVideoPath) {
|
||||
errorToast(i18n.t('Capture frame from this video not yet implemented'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const outPath = await captureFrame(customOutDir, filePath, videoRef.current, currentTimeRef.current, captureFormat);
|
||||
const mustCaptureFfmpeg = html5FriendlyPath || dummyVideoPath;
|
||||
const currentTime = currentTimeRef.current;
|
||||
const video = videoRef.current;
|
||||
const outPath = mustCaptureFfmpeg
|
||||
? await captureFrameFfmpeg({ customOutDir, videoPath: filePath, currentTime, captureFormat, duration: video.duration })
|
||||
: await captureFrameFromTag({ customOutDir, filePath, video, currentTime, captureFormat });
|
||||
|
||||
toast.fire({ icon: 'success', title: `${i18n.t('Screenshot captured to:')} ${outPath}` });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
|
|
@ -2,6 +2,8 @@ import strongDataUri from 'strong-data-uri';
|
|||
|
||||
import { formatDuration, getOutPath, transferTimestampsWithOffset } from './util';
|
||||
|
||||
import { captureFrame as ffmpegCaptureFrame } from './ffmpeg';
|
||||
|
||||
const fs = window.require('fs-extra');
|
||||
const mime = window.require('mime-types');
|
||||
|
||||
|
@ -17,7 +19,21 @@ function getFrameFromVideo(video, format) {
|
|||
return strongDataUri.decode(dataUri);
|
||||
}
|
||||
|
||||
export default async function captureFrame(customOutDir, filePath, video, currentTime, captureFormat) {
|
||||
async function transferTimestamps({ duration, currentTime, fromPath, toPath }) {
|
||||
const offset = -duration + currentTime;
|
||||
await transferTimestampsWithOffset(fromPath, toPath, offset);
|
||||
}
|
||||
|
||||
export async function captureFrameFfmpeg({ customOutDir, videoPath, currentTime, captureFormat, duration }) {
|
||||
const time = formatDuration({ seconds: currentTime, fileNameFriendly: true });
|
||||
|
||||
const outPath = getOutPath(customOutDir, videoPath, `${time}.${captureFormat}`);
|
||||
await ffmpegCaptureFrame({ timestamp: currentTime, videoPath, outPath });
|
||||
await transferTimestamps({ duration, currentTime, fromPath: videoPath, toPath: outPath });
|
||||
return outPath;
|
||||
}
|
||||
|
||||
export async function captureFrameFromTag({ customOutDir, filePath, video, currentTime, captureFormat }) {
|
||||
const buf = getFrameFromVideo(video, captureFormat);
|
||||
|
||||
const ext = mime.extension(buf.mimetype);
|
||||
|
@ -25,7 +41,7 @@ export default async function captureFrame(customOutDir, filePath, video, curren
|
|||
|
||||
const outPath = getOutPath(customOutDir, filePath, `${time}.${ext}`);
|
||||
await fs.writeFile(outPath, buf);
|
||||
const offset = -video.duration + currentTime;
|
||||
await transferTimestampsWithOffset(filePath, outPath, offset);
|
||||
|
||||
await transferTimestamps({ duration: video.duration, currentTime, fromPath: filePath, toPath: outPath });
|
||||
return outPath;
|
||||
}
|
||||
|
|
|
@ -631,6 +631,20 @@ export async function renderFrame(timestamp, filePath, rotation) {
|
|||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
// see capture-frame.js
|
||||
export async function captureFrame({ timestamp, videoPath, outPath }) {
|
||||
const args = [
|
||||
'-ss', timestamp,
|
||||
'-i', videoPath,
|
||||
'-vframes', '1',
|
||||
'-q:v', '3',
|
||||
'-y', outPath,
|
||||
];
|
||||
|
||||
const ffmpegPath = getFfmpegPath();
|
||||
await execa(ffmpegPath, args, { encoding: null });
|
||||
}
|
||||
|
||||
// https://www.ffmpeg.org/doxygen/3.2/libavutil_2utils_8c_source.html#l00079
|
||||
export const defaultProcessedCodecTypes = [
|
||||
'video',
|
||||
|
|
Ładowanie…
Reference in New Issue