-
-
Notifications
You must be signed in to change notification settings - Fork 975
Description
Describe the bug
I am trying to get those 5 params: width: number; height: number; fps: number; totalFrame: number; sizeMb: number; durationSecond: number;
- The user uploads the video using upload from antd (package version "^5.25.4")
- [email protected], [email protected], ffmpeg/[email protected]", ffmpeg/util@^0.12.2".
- I don't have the user's full file path because of a security issue in the browser.
- The method "getVideoDetail" is working, but when the video is cropped, it gives me the wrong fps
- The method "getVideoDetailFfmpeg" is giving me this error: "FFmpeg error: ErrnoError: FS error"
PS: I'm new to Javascript and not sure what I'm doing wrong, or is it possible to do this on Javascript, please help me
To Reproduce
`// --- test-page-view.tsx ---
import React, { useRef, useState } from 'react';
import { Button, Card, Flex, Upload, Modal } from 'antd';
import { CloudUploadOutlined } from '@ant-design/icons';
import { FFmpeg } from '@ffmpeg/ffmpeg';
import { fetchFile, toBlobURL } from '@ffmpeg/util';
const ffmpeg = new FFmpeg();
export const getVideoDetailFfmpeg = async (
file: File
): Promise<{
width: number;
height: number;
fps: number;
totalFrame: number;
sizeMb: number;
durationSecond: number;
}> => {
await ffmpeg.load();
const fileName = file.name;
// 2) write file into MEMFS
await ffmpeg.writeFile(fileName, await fetchFile(file));
try {
// 3) exec ffprobe‐style JSON dump
await ffmpeg.exec([
'-i', fileName,
'-hide_banner',
'-loglevel', 'error',
'-select_streams', 'v:0',
'-show_entries', 'stream=width,height,r_frame_rate,nb_frames',
'-show_entries', 'format=duration,size',
'-of', 'json',
'metadata.json',
]);
// 4) read + parse
const rawData = await ffmpeg.readFile('metadata.json');
const rawBytes = typeof rawData === 'string'
? new TextEncoder().encode(rawData)
: rawData;
const { streams, format } = JSON.parse(new TextDecoder().decode(rawBytes));
const stream = streams[0];
// 5) compute
const safeDiv = (ratio: string) => {
const [n, d] = ratio.split('/').map(Number);
return n / (d || 1);
};
const fps = safeDiv(stream.r_frame_rate);
const durationSecond = Math.floor(Number(format.duration));
const sizeMb = Number(format.size) / 1_000_000;
const totalFrame = stream.nb_frames
? Number(stream.nb_frames)
: Math.floor(durationSecond * fps);
return {
width: stream.width,
height: stream.height,
fps: parseFloat(fps.toFixed(2)),
totalFrame,
sizeMb: parseFloat(sizeMb.toFixed(2)),
durationSecond,
};
} catch (err) {
console.error('FFmpeg error:', err);
throw err;
}
};
const getVideoDetail = (file: File): Promise<{
width: number;
height: number;
fps: number;
totalFrame: number;
sizeMb: number;
durationSecond: number;
}> => {
return new Promise((resolve, reject) => {
const video = document.createElement('video');
const url = URL.createObjectURL(file);
console.log('Video URL:', url);
video.src = url;
video.muted = true;
video.preload = 'metadata';
video.addEventListener('loadedmetadata', () => {
const width = video.videoWidth;
const height = video.videoHeight;
const durationSecond = Math.ceil(video.duration);
const sizeMb = parseFloat((file.size / (1024 * 1024)).toFixed(2));
const frameTimes: number[] = [];
const maxFrames = 60;
const onFrame = (_: number, metadata: VideoFrameCallbackMetadata) => {
frameTimes.push(metadata.mediaTime);
if (frameTimes.length < maxFrames) {
video.requestVideoFrameCallback(onFrame);
return;
}
const intervals = frameTimes.slice(1).map((t, i) => t - frameTimes[i]);
const avgInterval = intervals.reduce((a, b) => a + b, 0) / intervals.length;
const fps = parseFloat((1 / avgInterval).toFixed(2));
const totalFrame = Math.round(durationSecond * fps);
console.log('FPS frame interval:', fps, intervals.length, durationSecond);
resolve({
width,
height,
fps,
totalFrame,
sizeMb: Number(sizeMb),
durationSecond: parseFloat(durationSecond.toFixed(2))
});
URL.revokeObjectURL(url);
};
video.play().then(() => video.requestVideoFrameCallback(onFrame)).catch(reject);
});
video.addEventListener('error', reject);
});
};
export const TestPageView: React.FC = () => {
const [open, setOpen] = useState(false);
const [fileList, setFileList] = useState<File[]>([]);
const [videoDetails, setVideoDetails] = useState<{
width: number;
height: number;
fps: number;
totalFrame: number;
sizeMb: number;
durationSecond: number;
} | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const uploadRef = useRef<any>(null);
const handleChange = async (info: any) => {
const file = info.file;
if (!file?.type?.startsWith('video/')) {
setFileList([file]);
setOpen(true);
return;
}
getVideoDetailFfmpeg(file)
.then(setVideoDetails)
.catch(console.error)
.finally(() => {
setFileList([file]);
setOpen(true);
});
};
const closeModal = () => {
setOpen(false);
setFileList([]);
setVideoDetails(null);
if (fileInputRef.current) fileInputRef.current.value = '';
if (uploadRef.current) uploadRef.current.upload.uploader.fileInput.value = '';
};
return (
<Card>
<Flex gap="middle" justify='center'>
<Flex vertical gap="large" style={{ padding: 50 }}>
<Flex justify='center'>
<Upload
ref={uploadRef}
name="file"
listType="picture-circle"
showUploadList={false}
accept="video/*"
onChange={handleChange}
>
<button style={{ border: 0, background: 'none' }} type="button">
{/* Fixed by adding rev prop */}
<CloudUploadOutlined rev={undefined} style={{ fontSize: '32px' }} />
</button>
</Upload>
</Flex>
<Flex justify='center' gap="middle">
<Button onClick={() => fileInputRef.current?.click()}>Browse File</Button>
<input
type="file"
ref={fileInputRef}
hidden
accept="video/*"
onChange={e => e.target.files?.[0] && handleChange({ file: e.target.files[0] })}
/>
</Flex>
</Flex>
</Flex>
<Modal
title="Upload Video"
open={open}
onCancel={closeModal}
onOk={() => {
console.log('Uploading:', fileList);
closeModal();
}}
>
{fileList[0] ? (
<div>
<p>Selected file: {fileList[0].name}</p>
{videoDetails ? (
<>
<p>Dimensions: {videoDetails.width} × {videoDetails.height}</p>
<p>FPS: {videoDetails.fps}</p>
<p>Total frames: {videoDetails.totalFrame}</p>
<p>Size: {videoDetails.sizeMb} MB</p>
<p>Duration: {videoDetails.durationSecond} seconds</p>
</>
) : (
<p>Loading video details...</p>
)}
</div>
) : (
<p>No file selected</p>
)}
</Modal>
</Card>
);
};`