(filePath)
| 445 | } |
| 446 | |
| 447 | async function ffprobe(filePath) { |
| 448 | const args = [ |
| 449 | "-v", "error", |
| 450 | "-show_entries", "stream=codec_type,width,height,r_frame_rate,avg_frame_rate,nb_frames,duration", |
| 451 | "-show_entries", "format=duration", |
| 452 | "-of", "json", |
| 453 | filePath |
| 454 | ]; |
| 455 | const { stdout } = await run(FFPROBE, args); |
| 456 | const parsed = JSON.parse(stdout.toString()); |
| 457 | const streams = parsed.streams || []; |
| 458 | const stream = streams.find(item => item.codec_type === "video") || {}; |
| 459 | const audio = streams.find(item => item.codec_type === "audio"); |
| 460 | const duration = Number(stream.duration || parsed.format?.duration || 0); |
| 461 | const fps = ratioToNumber(stream.avg_frame_rate || stream.r_frame_rate || "24/1") || 24; |
| 462 | const frames = Number(stream.nb_frames) || Math.round(duration * fps); |
| 463 | return { |
| 464 | width: Number(stream.width), |
| 465 | height: Number(stream.height), |
| 466 | fps, |
| 467 | frames, |
| 468 | duration, |
| 469 | hasAudio: Boolean(audio) |
| 470 | }; |
| 471 | } |
| 472 | |
| 473 | function ratioToNumber(value) { |
| 474 | const [a, b] = String(value).split("/").map(Number); |
no test coverage detected