larkup 0.1.46 → 0.1.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +12 -12
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/required-server-files.js +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +16 -16
- package/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
- package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/analytics/page_client-reference-manifest.js +1 -1
- package/.next/server/app/analytics.html +1 -1
- package/.next/server/app/analytics.rsc +17 -17
- package/.next/server/app/analytics.segments/_full.segment.rsc +17 -17
- package/.next/server/app/analytics.segments/_head.segment.rsc +4 -4
- package/.next/server/app/analytics.segments/_index.segment.rsc +11 -11
- package/.next/server/app/analytics.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/analytics.segments/analytics/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/analytics.segments/analytics.segment.rsc +3 -3
- package/.next/server/app/api/chat/route.js +9 -9
- package/.next/server/app/api/chat/route.js.nft.json +1 -1
- package/.next/server/app/api/deploy/ssh/route.js +2 -2
- package/.next/server/app/api/deploy/ssh/route.js.nft.json +1 -1
- package/.next/server/app/api/doc-editor/edit/route.js +2 -2
- package/.next/server/app/api/doc-editor/export/route.js +2 -2
- package/.next/server/app/api/doc-editor/extract-text/route.js +2 -2
- package/.next/server/app/api/doc-editor/parse/route.js +1 -1
- package/.next/server/app/api/doc-editor/restore/route.js +2 -2
- package/.next/server/app/api/document/sign/route.js +3 -3
- package/.next/server/app/api/marketplace/[toolId]/route.js.nft.json +1 -1
- package/.next/server/app/api/marketplace/route.js.nft.json +1 -1
- package/.next/server/app/api/media/process/route.js.nft.json +1 -1
- package/.next/server/app/api/media/route.js.nft.json +1 -1
- package/.next/server/app/api/parse-file/route.js +2 -2
- package/.next/server/app/api/server/download/route.js +1 -1
- package/.next/server/app/api/server/generate/route.js +2 -2
- package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
- package/.next/server/app/chat.html +1 -1
- package/.next/server/app/chat.rsc +17 -17
- package/.next/server/app/chat.segments/_full.segment.rsc +17 -17
- package/.next/server/app/chat.segments/_head.segment.rsc +4 -4
- package/.next/server/app/chat.segments/_index.segment.rsc +11 -11
- package/.next/server/app/chat.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/chat.segments/chat.segment.rsc +3 -3
- package/.next/server/app/data/page_client-reference-manifest.js +1 -1
- package/.next/server/app/data.html +1 -1
- package/.next/server/app/data.rsc +17 -17
- package/.next/server/app/data.segments/_full.segment.rsc +17 -17
- package/.next/server/app/data.segments/_head.segment.rsc +4 -4
- package/.next/server/app/data.segments/_index.segment.rsc +11 -11
- package/.next/server/app/data.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/data.segments/data/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/data.segments/data.segment.rsc +3 -3
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +16 -16
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/server/app/index.segments/_index.segment.rsc +11 -11
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings.html +1 -1
- package/.next/server/app/settings.rsc +18 -18
- package/.next/server/app/settings.segments/_full.segment.rsc +18 -18
- package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/server/app/settings.segments/_index.segment.rsc +11 -11
- package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/.next/server/chunks/[root-of-the-server]__05707i3._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0vmcld0._.js +3 -0
- package/.next/{standalone/apps/web/.next/server/chunks/[root-of-the-server]__08f60c7._.js → server/chunks/[root-of-the-server]__1e8p5bd._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__1ks-3_h._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1lhfkyh._.js +1 -1
- package/.next/server/chunks/packages_marketplace_src_125_g2p._.js +1 -1
- package/.next/server/chunks/packages_scraper_src_0kkcv_v._.js +1 -1
- package/.next/server/chunks/packages_tools_video-audio_dist_ffmpeg-spawn_0by2bau.js +3 -0
- package/.next/server/chunks/ssr/_1yqlx1m._.js +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/standalone/apps/web/.next/BUILD_ID +1 -1
- package/.next/standalone/apps/web/.next/build-manifest.json +3 -3
- package/.next/standalone/apps/web/.next/required-server-files.json +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/_not-found.rsc +16 -16
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/apps/web/.next/server/app/analytics.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/analytics.rsc +17 -17
- package/.next/standalone/apps/web/.next/server/app/analytics.segments/_full.segment.rsc +17 -17
- package/.next/standalone/apps/web/.next/server/app/analytics.segments/_head.segment.rsc +4 -4
- package/.next/standalone/apps/web/.next/server/app/analytics.segments/_index.segment.rsc +11 -11
- package/.next/standalone/apps/web/.next/server/app/analytics.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/analytics.segments/analytics/__PAGE__.segment.rsc +3 -3
- package/.next/standalone/apps/web/.next/server/app/analytics.segments/analytics.segment.rsc +3 -3
- package/.next/standalone/apps/web/.next/server/app/api/chat/route.js +9 -9
- package/.next/standalone/apps/web/.next/server/app/api/chat/route.js.nft.json +1 -1
- package/.next/standalone/apps/web/.next/server/app/api/deploy/ssh/route.js +2 -2
- package/.next/standalone/apps/web/.next/server/app/api/deploy/ssh/route.js.nft.json +1 -1
- package/.next/standalone/apps/web/.next/server/app/api/doc-editor/edit/route.js +2 -2
- package/.next/standalone/apps/web/.next/server/app/api/doc-editor/export/route.js +2 -2
- package/.next/standalone/apps/web/.next/server/app/api/doc-editor/extract-text/route.js +2 -2
- package/.next/standalone/apps/web/.next/server/app/api/doc-editor/parse/route.js +1 -1
- package/.next/standalone/apps/web/.next/server/app/api/doc-editor/restore/route.js +2 -2
- package/.next/standalone/apps/web/.next/server/app/api/document/sign/route.js +3 -3
- package/.next/standalone/apps/web/.next/server/app/api/marketplace/[toolId]/route.js.nft.json +1 -1
- package/.next/standalone/apps/web/.next/server/app/api/marketplace/route.js.nft.json +1 -1
- package/.next/standalone/apps/web/.next/server/app/api/media/process/route.js.nft.json +1 -1
- package/.next/standalone/apps/web/.next/server/app/api/media/route.js.nft.json +1 -1
- package/.next/standalone/apps/web/.next/server/app/api/parse-file/route.js +2 -2
- package/.next/standalone/apps/web/.next/server/app/api/server/download/route.js +1 -1
- package/.next/standalone/apps/web/.next/server/app/api/server/generate/route.js +2 -2
- package/.next/standalone/apps/web/.next/server/app/chat/page_client-reference-manifest.js +1 -1
- package/.next/standalone/apps/web/.next/server/app/chat.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/chat.rsc +17 -17
- package/.next/standalone/apps/web/.next/server/app/chat.segments/_full.segment.rsc +17 -17
- package/.next/standalone/apps/web/.next/server/app/chat.segments/_head.segment.rsc +4 -4
- package/.next/standalone/apps/web/.next/server/app/chat.segments/_index.segment.rsc +11 -11
- package/.next/standalone/apps/web/.next/server/app/chat.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +3 -3
- package/.next/standalone/apps/web/.next/server/app/chat.segments/chat.segment.rsc +3 -3
- package/.next/standalone/apps/web/.next/server/app/data/page_client-reference-manifest.js +1 -1
- package/.next/standalone/apps/web/.next/server/app/data.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/data.rsc +17 -17
- package/.next/standalone/apps/web/.next/server/app/data.segments/_full.segment.rsc +17 -17
- package/.next/standalone/apps/web/.next/server/app/data.segments/_head.segment.rsc +4 -4
- package/.next/standalone/apps/web/.next/server/app/data.segments/_index.segment.rsc +11 -11
- package/.next/standalone/apps/web/.next/server/app/data.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/data.segments/data/__PAGE__.segment.rsc +3 -3
- package/.next/standalone/apps/web/.next/server/app/data.segments/data.segment.rsc +3 -3
- package/.next/standalone/apps/web/.next/server/app/index.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/index.rsc +16 -16
- package/.next/standalone/apps/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/apps/web/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/.next/standalone/apps/web/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/apps/web/.next/server/app/index.segments/_index.segment.rsc +11 -11
- package/.next/standalone/apps/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.html +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.rsc +18 -18
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_full.segment.rsc +18 -18
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_head.segment.rsc +4 -4
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_index.segment.rsc +11 -11
- package/.next/standalone/apps/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/apps/web/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
- package/.next/standalone/apps/web/.next/server/app/settings.segments/settings.segment.rsc +3 -3
- package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__05707i3._.js +1 -1
- package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__0vmcld0._.js +3 -0
- package/.next/{server/chunks/[root-of-the-server]__08f60c7._.js → standalone/apps/web/.next/server/chunks/[root-of-the-server]__1e8p5bd._.js} +2 -2
- package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__1ks-3_h._.js +3 -0
- package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__1lhfkyh._.js +1 -1
- package/.next/standalone/apps/web/.next/server/chunks/packages_marketplace_src_125_g2p._.js +1 -1
- package/.next/standalone/apps/web/.next/server/chunks/packages_scraper_src_0kkcv_v._.js +1 -1
- package/.next/standalone/apps/web/.next/server/chunks/packages_tools_video-audio_dist_ffmpeg-spawn_0by2bau.js +3 -0
- package/.next/standalone/apps/web/.next/server/chunks/ssr/_1yqlx1m._.js +1 -1
- package/.next/standalone/apps/web/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
- package/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
- package/.next/standalone/apps/web/.next/static/chunks/{0wy4vhj2gft06.js → 2azppfcsqga3r.js} +1 -1
- package/.next/standalone/apps/web/CHANGELOG.md +9 -0
- package/.next/standalone/apps/web/components/update-banner.tsx +2 -2
- package/.next/standalone/apps/web/package.json +1 -1
- package/.next/standalone/apps/web/server.js +1 -1
- package/.next/standalone/packages/tools/video-audio/CHANGELOG.md +6 -0
- package/.next/standalone/packages/tools/video-audio/dist/audio-processor.js +30 -32
- package/.next/standalone/packages/tools/video-audio/dist/ffmpeg-spawn.js +127 -0
- package/.next/standalone/packages/tools/video-audio/dist/video-processor.js +95 -113
- package/.next/standalone/packages/tools/video-audio/package.json +2 -7
- package/.next/standalone/packages/tools/video-audio/src/audio-processor.ts +30 -31
- package/.next/standalone/packages/tools/video-audio/src/ffmpeg-spawn.ts +176 -0
- package/.next/standalone/packages/tools/video-audio/src/video-processor.ts +88 -116
- package/.next/static/chunks/{0wy4vhj2gft06.js → 2azppfcsqga3r.js} +1 -1
- package/CHANGELOG.md +9 -0
- package/LICENSE +176 -0
- package/components/update-banner.tsx +2 -2
- package/package.json +4 -4
- package/.next/server/chunks/[externals]__1_tvo45._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__010rwkn._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0xbvdcw._.js +0 -3
- package/.next/server/chunks/node_modules__pnpm_0ddqx71._.js +0 -3
- package/.next/standalone/apps/web/.next/server/chunks/[externals]__1_tvo45._.js +0 -3
- package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__010rwkn._.js +0 -3
- package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__0xbvdcw._.js +0 -3
- package/.next/standalone/apps/web/.next/server/chunks/node_modules__pnpm_0ddqx71._.js +0 -3
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/capabilities.js +0 -665
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/ffprobe.js +0 -261
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/fluent-ffmpeg.js +0 -226
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/audio.js +0 -178
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/custom.js +0 -212
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/inputs.js +0 -178
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/misc.js +0 -41
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/output.js +0 -162
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/video.js +0 -184
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/videosize.js +0 -291
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/presets/divx.js +0 -14
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/presets/flashvideo.js +0 -16
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/presets/podcast.js +0 -16
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/processor.js +0 -662
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/recipes.js +0 -456
- package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/utils.js +0 -455
- package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/CHANGELOG.md +0 -152
- package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/LICENSE +0 -15
- package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/README.md +0 -51
- package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/bin/which +0 -52
- package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/package.json +0 -30
- package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/which.js +0 -135
- /package/.next/standalone/apps/web/.next/static/{HU74sZ8oKr0VlhdDfm98x → PM7-3u8cSkShjD80zaJ4l}/_buildManifest.js +0 -0
- /package/.next/standalone/apps/web/.next/static/{HU74sZ8oKr0VlhdDfm98x → PM7-3u8cSkShjD80zaJ4l}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/apps/web/.next/static/{HU74sZ8oKr0VlhdDfm98x → PM7-3u8cSkShjD80zaJ4l}/_ssgManifest.js +0 -0
- /package/.next/static/{HU74sZ8oKr0VlhdDfm98x → PM7-3u8cSkShjD80zaJ4l}/_buildManifest.js +0 -0
- /package/.next/static/{HU74sZ8oKr0VlhdDfm98x → PM7-3u8cSkShjD80zaJ4l}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{HU74sZ8oKr0VlhdDfm98x → PM7-3u8cSkShjD80zaJ4l}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight ffmpeg/ffprobe helpers using child_process.spawn.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the deprecated `fluent-ffmpeg` library with direct process
|
|
5
|
+
* spawning for maximum compatibility and zero third-party runtime deps.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
/* ------------------------------------------------------------------ */
|
|
9
|
+
/* Binary resolution */
|
|
10
|
+
/* ------------------------------------------------------------------ */
|
|
11
|
+
let _ffmpegPath = null;
|
|
12
|
+
let _ffprobePath = null;
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the ffmpeg binary path. Prefers the system binary on PATH;
|
|
15
|
+
* falls back to `@ffmpeg-installer/ffmpeg` if installed.
|
|
16
|
+
*/
|
|
17
|
+
function resolveFfmpegPath() {
|
|
18
|
+
if (_ffmpegPath)
|
|
19
|
+
return _ffmpegPath;
|
|
20
|
+
// Try @ffmpeg-installer first (bundled binary)
|
|
21
|
+
try {
|
|
22
|
+
const installerPath = require.resolve('@ffmpeg-installer/ffmpeg');
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
24
|
+
const installer = require(installerPath);
|
|
25
|
+
if (installer?.path) {
|
|
26
|
+
_ffmpegPath = installer.path;
|
|
27
|
+
return _ffmpegPath;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Not installed — fall through to system binary
|
|
32
|
+
}
|
|
33
|
+
_ffmpegPath = 'ffmpeg';
|
|
34
|
+
return _ffmpegPath;
|
|
35
|
+
}
|
|
36
|
+
function resolveFfprobePath() {
|
|
37
|
+
if (_ffprobePath)
|
|
38
|
+
return _ffprobePath;
|
|
39
|
+
// Try @ffprobe-installer first
|
|
40
|
+
try {
|
|
41
|
+
const installerPath = require.resolve('@ffprobe-installer/ffprobe');
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
43
|
+
const installer = require(installerPath);
|
|
44
|
+
if (installer?.path) {
|
|
45
|
+
_ffprobePath = installer.path;
|
|
46
|
+
return _ffprobePath;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Not installed — fall through
|
|
51
|
+
}
|
|
52
|
+
_ffprobePath = 'ffprobe';
|
|
53
|
+
return _ffprobePath;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Probe a media file for metadata using ffprobe.
|
|
57
|
+
*/
|
|
58
|
+
export function ffprobe(filePath) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const args = [
|
|
61
|
+
'-v',
|
|
62
|
+
'quiet',
|
|
63
|
+
'-print_format',
|
|
64
|
+
'json',
|
|
65
|
+
'-show_format',
|
|
66
|
+
'-show_streams',
|
|
67
|
+
filePath,
|
|
68
|
+
];
|
|
69
|
+
const proc = spawn(resolveFfprobePath(), args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
70
|
+
const chunks = [];
|
|
71
|
+
let stderr = '';
|
|
72
|
+
proc.stdout.on('data', (data) => chunks.push(data));
|
|
73
|
+
proc.stderr.on('data', (data) => {
|
|
74
|
+
stderr += data.toString();
|
|
75
|
+
});
|
|
76
|
+
proc.on('close', (code) => {
|
|
77
|
+
if (code !== 0) {
|
|
78
|
+
return reject(new Error(`ffprobe exited with code ${code}: ${stderr}`));
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const json = JSON.parse(Buffer.concat(chunks).toString());
|
|
82
|
+
resolve(json);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
reject(new Error(`Failed to parse ffprobe output: ${err}`));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
proc.on('error', reject);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Run an ffmpeg command and return a promise that resolves on success.
|
|
93
|
+
*/
|
|
94
|
+
export function runFfmpeg(options) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const args = ['-y', ...options.args];
|
|
97
|
+
const proc = spawn(resolveFfmpegPath(), args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
98
|
+
let stderrBuf = '';
|
|
99
|
+
proc.stderr.on('data', (data) => {
|
|
100
|
+
stderrBuf += data.toString();
|
|
101
|
+
// Process line by line
|
|
102
|
+
const lines = stderrBuf.split('\n');
|
|
103
|
+
stderrBuf = lines.pop() ?? '';
|
|
104
|
+
for (const line of lines) {
|
|
105
|
+
options.onStderr?.(line);
|
|
106
|
+
// Parse progress: "time=HH:MM:SS.ms" or "time=SS.ms"
|
|
107
|
+
if (options.onProgress && options.durationSecs && options.durationSecs > 0) {
|
|
108
|
+
const timeMatch = line.match(/time=(\d+):(\d+):(\d+)\.(\d+)/);
|
|
109
|
+
if (timeMatch) {
|
|
110
|
+
const secs = parseInt(timeMatch[1], 10) * 3600 +
|
|
111
|
+
parseInt(timeMatch[2], 10) * 60 +
|
|
112
|
+
parseInt(timeMatch[3], 10) +
|
|
113
|
+
parseInt(timeMatch[4], 10) / 100;
|
|
114
|
+
options.onProgress(Math.min(1, secs / options.durationSecs));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
proc.on('close', (code) => {
|
|
120
|
+
if (code !== 0) {
|
|
121
|
+
return reject(new Error(`ffmpeg exited with code ${code}`));
|
|
122
|
+
}
|
|
123
|
+
resolve();
|
|
124
|
+
});
|
|
125
|
+
proc.on('error', reject);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { promises as fs } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { ffprobe, runFfmpeg } from './ffmpeg-spawn';
|
|
3
4
|
export function extractRunningState(text) {
|
|
4
5
|
const stateLines = [];
|
|
5
6
|
const scorePatterns = [
|
|
@@ -182,11 +183,10 @@ export function createEndingSamplingPlan(durationSecs, maxAdditionalFrames) {
|
|
|
182
183
|
};
|
|
183
184
|
}
|
|
184
185
|
export async function processVideo(videoPath, options) {
|
|
185
|
-
const ffmpeg = await importFfmpeg();
|
|
186
186
|
await fs.mkdir(options.outputDir, { recursive: true });
|
|
187
187
|
const framesDir = path.join(options.outputDir, 'frames');
|
|
188
188
|
await fs.mkdir(framesDir, { recursive: true });
|
|
189
|
-
const meta = await probeVideo(
|
|
189
|
+
const meta = await probeVideo(videoPath);
|
|
190
190
|
const audioPath = path.join(options.outputDir, 'audio.wav');
|
|
191
191
|
const interval = options.frameIntervalSecs ?? 10;
|
|
192
192
|
const maxFrames = options.maxFrames ?? 100;
|
|
@@ -197,7 +197,7 @@ export async function processVideo(videoPath, options) {
|
|
|
197
197
|
};
|
|
198
198
|
const extractAudioPromise = options.skipAudioExtraction
|
|
199
199
|
? Promise.resolve(undefined)
|
|
200
|
-
: extractAudio(
|
|
200
|
+
: extractAudio(videoPath, audioPath, options.threads, (progress) => {
|
|
201
201
|
audioProgress = Math.max(audioProgress, progress);
|
|
202
202
|
reportExtractionProgress();
|
|
203
203
|
})
|
|
@@ -235,9 +235,8 @@ export async function processVideo(videoPath, options) {
|
|
|
235
235
|
return { audioPath: extractedAudioPath, frames, meta };
|
|
236
236
|
}
|
|
237
237
|
export async function extractSceneFrames(videoPath, options) {
|
|
238
|
-
const ffmpeg = await importFfmpeg();
|
|
239
238
|
await fs.mkdir(options.outputDir, { recursive: true });
|
|
240
|
-
const duration = options.durationSecs || (await probeVideo(
|
|
239
|
+
const duration = options.durationSecs || (await probeVideo(videoPath)).durationSecs;
|
|
241
240
|
const plan = createVideoSamplingPlan(duration, options.intervalSecs, options.maxFrames);
|
|
242
241
|
const totalPlannedWork = Math.max(1, plan.estimatedFrameCount);
|
|
243
242
|
const periodicWeight = plan.periodicFrameCount / totalPlannedWork;
|
|
@@ -274,7 +273,7 @@ export async function extractSceneFrames(videoPath, options) {
|
|
|
274
273
|
const periodicFrames = plan.periodicFrameCount === 0
|
|
275
274
|
? []
|
|
276
275
|
: duration >= 2 * 60 * 60
|
|
277
|
-
? await extractFramesAtTimestamps(
|
|
276
|
+
? await extractFramesAtTimestamps(videoPath, Array.from({ length: plan.periodicFrameCount }, (_, index) => Math.min(Math.max(0, duration - 1), index * plan.periodicIntervalSecs)), {
|
|
278
277
|
outputDir: options.outputDir,
|
|
279
278
|
threads: options.threads,
|
|
280
279
|
onProgress: (progress) => reportProgress(progress * periodicWeight),
|
|
@@ -294,38 +293,40 @@ export async function extractSceneFrames(videoPath, options) {
|
|
|
294
293
|
await fs.mkdir(sceneDir, { recursive: true });
|
|
295
294
|
const pattern = path.join(sceneDir, 'scene_%04d.jpg');
|
|
296
295
|
let timestamps = [];
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
])
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
|
|
296
|
+
const isLongRecording = duration >= 2 * 60 * 60;
|
|
297
|
+
const vf = isLongRecording
|
|
298
|
+
? `scale='min(640,iw)':-2,select='gt(scene,${threshold})*if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${plan.minimumSceneGapSecs}))',showinfo`
|
|
299
|
+
: `select='gt(scene,${threshold})*if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${plan.minimumSceneGapSecs}))',showinfo,scale='min(1280,iw)':-2`;
|
|
300
|
+
const sceneArgs = [
|
|
301
|
+
...(isLongRecording ? ['-skip_frame', 'nokey'] : []),
|
|
302
|
+
'-i',
|
|
303
|
+
videoPath,
|
|
304
|
+
'-vf',
|
|
305
|
+
vf,
|
|
306
|
+
'-vsync',
|
|
307
|
+
'vfr',
|
|
308
|
+
'-frames:v',
|
|
309
|
+
String(plan.sceneFrameCount),
|
|
310
|
+
...(options.threads ? ['-threads', String(options.threads)] : []),
|
|
311
|
+
pattern,
|
|
312
|
+
];
|
|
313
|
+
try {
|
|
314
|
+
await runFfmpeg({
|
|
315
|
+
args: sceneArgs,
|
|
316
|
+
durationSecs: duration,
|
|
317
|
+
onProgress: (percent) => {
|
|
318
|
+
reportProgress(periodicWeight + percent * sceneWeight);
|
|
319
|
+
},
|
|
320
|
+
onStderr: (line) => {
|
|
321
|
+
const match = line.match(/pts_time:([0-9.]+)/);
|
|
322
|
+
if (match)
|
|
323
|
+
timestamps.push(Number(match[1]));
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// Scene detection may fail on some codecs — continue with periodic frames
|
|
329
|
+
}
|
|
329
330
|
reportProgress(periodicWeight + sceneWeight);
|
|
330
331
|
const files = (await fs.readdir(sceneDir))
|
|
331
332
|
.filter((name) => /^scene_\d+\.jpg$/.test(name))
|
|
@@ -342,7 +343,7 @@ export async function extractSceneFrames(videoPath, options) {
|
|
|
342
343
|
const deduplicated = merged.filter((frame, index) => index === 0 || Math.abs(frame.timestampSecs - merged[index - 1].timestampSecs) >= 1);
|
|
343
344
|
return addEndingEvidence(deduplicated);
|
|
344
345
|
}
|
|
345
|
-
async function extractFramesAtTimestamps(
|
|
346
|
+
async function extractFramesAtTimestamps(videoPath, timestamps, options) {
|
|
346
347
|
await fs.mkdir(options.outputDir, { recursive: true });
|
|
347
348
|
const results = new Array(timestamps.length);
|
|
348
349
|
let cursor = 0;
|
|
@@ -352,19 +353,19 @@ async function extractFramesAtTimestamps(ffmpeg, videoPath, timestamps, options)
|
|
|
352
353
|
const index = cursor++;
|
|
353
354
|
const timestampSecs = timestamps[index];
|
|
354
355
|
const outputPath = path.join(options.outputDir, `frame_${String(index + 1).padStart(4, '0')}.jpg`);
|
|
355
|
-
await
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
'-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
.
|
|
366
|
-
|
|
367
|
-
|
|
356
|
+
await runFfmpeg({
|
|
357
|
+
args: [
|
|
358
|
+
'-ss',
|
|
359
|
+
String(timestampSecs),
|
|
360
|
+
'-i',
|
|
361
|
+
videoPath,
|
|
362
|
+
'-vf',
|
|
363
|
+
`scale='min(1280,iw)':-2`,
|
|
364
|
+
'-frames:v',
|
|
365
|
+
'1',
|
|
366
|
+
...(options.threads ? ['-threads', String(options.threads)] : []),
|
|
367
|
+
outputPath,
|
|
368
|
+
],
|
|
368
369
|
});
|
|
369
370
|
results[index] = { path: outputPath, timestampSecs };
|
|
370
371
|
completed += 1;
|
|
@@ -374,37 +375,32 @@ async function extractFramesAtTimestamps(ffmpeg, videoPath, timestamps, options)
|
|
|
374
375
|
return results;
|
|
375
376
|
}
|
|
376
377
|
export async function extractFrames(videoPath, options) {
|
|
377
|
-
const ffmpeg = await importFfmpeg();
|
|
378
378
|
await fs.mkdir(options.outputDir, { recursive: true });
|
|
379
379
|
let duration = options.durationSecs;
|
|
380
380
|
if (!duration) {
|
|
381
|
-
const meta = await probeVideo(
|
|
381
|
+
const meta = await probeVideo(videoPath);
|
|
382
382
|
duration = meta.durationSecs;
|
|
383
383
|
}
|
|
384
384
|
const startSecs = Math.max(0, Math.min(options.startSecs ?? 0, duration));
|
|
385
385
|
const rangeDuration = Math.max(0, duration - startSecs);
|
|
386
386
|
const frameCount = Math.max(1, Math.min(Math.ceil(rangeDuration / options.intervalSecs), options.maxFrames));
|
|
387
387
|
const pattern = path.join(options.outputDir, 'frame_%04d.jpg');
|
|
388
|
-
await
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
.
|
|
395
|
-
'-vsync
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
.
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
})
|
|
405
|
-
.on('end', () => resolve())
|
|
406
|
-
.on('error', (err) => reject(err))
|
|
407
|
-
.run();
|
|
388
|
+
await runFfmpeg({
|
|
389
|
+
args: [
|
|
390
|
+
...(startSecs > 0 ? ['-ss', String(startSecs)] : []),
|
|
391
|
+
'-i',
|
|
392
|
+
videoPath,
|
|
393
|
+
'-vf',
|
|
394
|
+
`select='if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${options.intervalSecs}))',scale='min(1280,iw)':-2`,
|
|
395
|
+
'-vsync',
|
|
396
|
+
'vfr',
|
|
397
|
+
'-frames:v',
|
|
398
|
+
String(frameCount),
|
|
399
|
+
...(options.threads ? ['-threads', String(options.threads)] : []),
|
|
400
|
+
pattern,
|
|
401
|
+
],
|
|
402
|
+
durationSecs: rangeDuration,
|
|
403
|
+
onProgress: options.onProgress,
|
|
408
404
|
});
|
|
409
405
|
const files = (await fs.readdir(options.outputDir))
|
|
410
406
|
.filter((name) => /^frame_\d+\.jpg$/.test(name))
|
|
@@ -415,45 +411,31 @@ export async function extractFrames(videoPath, options) {
|
|
|
415
411
|
timestampSecs: startSecs + index * options.intervalSecs,
|
|
416
412
|
}));
|
|
417
413
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
.output(outputPath)
|
|
435
|
-
.on('progress', (progress) => {
|
|
436
|
-
if (typeof progress.percent === 'number') {
|
|
437
|
-
onProgress?.(Math.min(1, progress.percent / 100));
|
|
438
|
-
}
|
|
439
|
-
})
|
|
440
|
-
.on('end', () => resolve())
|
|
441
|
-
.on('error', (err) => reject(err))
|
|
442
|
-
.run();
|
|
414
|
+
function extractAudio(videoPath, outputPath, threads, onProgress) {
|
|
415
|
+
return runFfmpeg({
|
|
416
|
+
args: [
|
|
417
|
+
'-i',
|
|
418
|
+
videoPath,
|
|
419
|
+
'-vn',
|
|
420
|
+
'-ac',
|
|
421
|
+
'1',
|
|
422
|
+
'-ar',
|
|
423
|
+
'16000',
|
|
424
|
+
'-f',
|
|
425
|
+
'wav',
|
|
426
|
+
...(threads ? ['-threads', String(threads)] : []),
|
|
427
|
+
outputPath,
|
|
428
|
+
],
|
|
429
|
+
onProgress,
|
|
443
430
|
});
|
|
444
431
|
}
|
|
445
|
-
function probeVideo(
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
height: videoStream?.height ?? 0,
|
|
455
|
-
codec: videoStream?.codec_name ?? 'unknown',
|
|
456
|
-
});
|
|
457
|
-
});
|
|
458
|
-
});
|
|
432
|
+
async function probeVideo(videoPath) {
|
|
433
|
+
const data = await ffprobe(videoPath);
|
|
434
|
+
const videoStream = data.streams?.find((s) => s.codec_type === 'video');
|
|
435
|
+
return {
|
|
436
|
+
durationSecs: parseFloat(data.format?.duration ?? '0'),
|
|
437
|
+
width: videoStream?.width ?? 0,
|
|
438
|
+
height: videoStream?.height ?? 0,
|
|
439
|
+
codec: videoStream?.codec_name ?? 'unknown',
|
|
440
|
+
};
|
|
459
441
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@larkup/tool-video-audio",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -25,11 +25,6 @@
|
|
|
25
25
|
"build": "tsc --outDir dist",
|
|
26
26
|
"type-check": "tsc --noEmit"
|
|
27
27
|
},
|
|
28
|
-
"dependencies": {},
|
|
29
|
-
"optionalDependencies": {
|
|
30
|
-
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
|
31
|
-
"fluent-ffmpeg": "^2.1.3"
|
|
32
|
-
},
|
|
33
28
|
"peerDependencies": {
|
|
34
29
|
"@larkup/marketplace": ">=0.1.0"
|
|
35
30
|
},
|
|
@@ -40,7 +35,7 @@
|
|
|
40
35
|
},
|
|
41
36
|
"devDependencies": {
|
|
42
37
|
"@larkup/marketplace": "workspace:*",
|
|
43
|
-
"@types/
|
|
38
|
+
"@types/node": "^26.1.2",
|
|
44
39
|
"typescript": "5.7.3"
|
|
45
40
|
},
|
|
46
41
|
"license": "Apache-2.0",
|
|
@@ -595,18 +595,10 @@ function formatProviderName(provider: string): string {
|
|
|
595
595
|
.join(' ');
|
|
596
596
|
}
|
|
597
597
|
|
|
598
|
-
async function importFfmpeg() {
|
|
599
|
-
return import('fluent-ffmpeg');
|
|
600
|
-
}
|
|
601
|
-
|
|
602
598
|
async function probeAudioDuration(audioPath: string): Promise<number> {
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if (error) reject(error);
|
|
607
|
-
else resolve(Number(data.format?.duration ?? 0));
|
|
608
|
-
});
|
|
609
|
-
});
|
|
599
|
+
const { ffprobe } = await import('./ffmpeg-spawn');
|
|
600
|
+
const data = await ffprobe(audioPath);
|
|
601
|
+
return Number(data.format?.duration ?? 0);
|
|
610
602
|
}
|
|
611
603
|
|
|
612
604
|
async function splitAudio(
|
|
@@ -615,30 +607,37 @@ async function splitAudio(
|
|
|
615
607
|
segmentSecs: number,
|
|
616
608
|
onProgress?: (progress: number) => void | Promise<void>,
|
|
617
609
|
): Promise<string[]> {
|
|
618
|
-
const
|
|
610
|
+
const { runFfmpeg } = await import('./ffmpeg-spawn');
|
|
619
611
|
const path = await import('node:path');
|
|
620
612
|
const { promises: fs } = await import('node:fs');
|
|
621
613
|
const pattern = path.join(outputDir, 'part_%04d.mp3');
|
|
622
614
|
|
|
623
|
-
await
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
615
|
+
const duration = await probeAudioDuration(audioPath);
|
|
616
|
+
await runFfmpeg({
|
|
617
|
+
args: [
|
|
618
|
+
'-i',
|
|
619
|
+
audioPath,
|
|
620
|
+
'-vn',
|
|
621
|
+
'-ac',
|
|
622
|
+
'1',
|
|
623
|
+
'-ar',
|
|
624
|
+
'16000',
|
|
625
|
+
'-codec:a',
|
|
626
|
+
'libmp3lame',
|
|
627
|
+
'-b:a',
|
|
628
|
+
'32k',
|
|
629
|
+
'-f',
|
|
630
|
+
'segment',
|
|
631
|
+
'-segment_time',
|
|
632
|
+
String(segmentSecs),
|
|
633
|
+
'-reset_timestamps',
|
|
634
|
+
'1',
|
|
635
|
+
pattern,
|
|
636
|
+
],
|
|
637
|
+
durationSecs: duration,
|
|
638
|
+
onProgress: (percent) => {
|
|
639
|
+
void onProgress?.(Math.max(0, Math.min(1, percent)));
|
|
640
|
+
},
|
|
642
641
|
});
|
|
643
642
|
|
|
644
643
|
return (await fs.readdir(outputDir))
|