larkup 0.1.45 → 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/{041vy184s-7sh.js → 2azppfcsqga3r.js} +1 -1
- package/.next/standalone/apps/web/CHANGELOG.md +18 -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/{041vy184s-7sh.js → 2azppfcsqga3r.js} +1 -1
- package/CHANGELOG.md +18 -0
- package/LICENSE +176 -0
- package/components/update-banner.tsx +2 -2
- package/package.json +6 -6
- 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/{IHAOZOY0iJJ_cMmT7-lqT → PM7-3u8cSkShjD80zaJ4l}/_buildManifest.js +0 -0
- /package/.next/standalone/apps/web/.next/static/{IHAOZOY0iJJ_cMmT7-lqT → PM7-3u8cSkShjD80zaJ4l}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/apps/web/.next/static/{IHAOZOY0iJJ_cMmT7-lqT → PM7-3u8cSkShjD80zaJ4l}/_ssgManifest.js +0 -0
- /package/.next/static/{IHAOZOY0iJJ_cMmT7-lqT → PM7-3u8cSkShjD80zaJ4l}/_buildManifest.js +0 -0
- /package/.next/static/{IHAOZOY0iJJ_cMmT7-lqT → PM7-3u8cSkShjD80zaJ4l}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{IHAOZOY0iJJ_cMmT7-lqT → PM7-3u8cSkShjD80zaJ4l}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
|
|
10
|
+
/* ------------------------------------------------------------------ */
|
|
11
|
+
/* Binary resolution */
|
|
12
|
+
/* ------------------------------------------------------------------ */
|
|
13
|
+
|
|
14
|
+
let _ffmpegPath: string | null = null;
|
|
15
|
+
let _ffprobePath: string | null = null;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the ffmpeg binary path. Prefers the system binary on PATH;
|
|
19
|
+
* falls back to `@ffmpeg-installer/ffmpeg` if installed.
|
|
20
|
+
*/
|
|
21
|
+
function resolveFfmpegPath(): string {
|
|
22
|
+
if (_ffmpegPath) return _ffmpegPath;
|
|
23
|
+
|
|
24
|
+
// Try @ffmpeg-installer first (bundled binary)
|
|
25
|
+
try {
|
|
26
|
+
const installerPath = require.resolve('@ffmpeg-installer/ffmpeg');
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
28
|
+
const installer = require(installerPath) as { path?: string };
|
|
29
|
+
if (installer?.path) {
|
|
30
|
+
_ffmpegPath = installer.path;
|
|
31
|
+
return _ffmpegPath;
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
// Not installed — fall through to system binary
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
_ffmpegPath = 'ffmpeg';
|
|
38
|
+
return _ffmpegPath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function resolveFfprobePath(): string {
|
|
42
|
+
if (_ffprobePath) return _ffprobePath;
|
|
43
|
+
|
|
44
|
+
// Try @ffprobe-installer first
|
|
45
|
+
try {
|
|
46
|
+
const installerPath = require.resolve('@ffprobe-installer/ffprobe');
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
48
|
+
const installer = require(installerPath) as { path?: string };
|
|
49
|
+
if (installer?.path) {
|
|
50
|
+
_ffprobePath = installer.path;
|
|
51
|
+
return _ffprobePath;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
// Not installed — fall through
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_ffprobePath = 'ffprobe';
|
|
58
|
+
return _ffprobePath;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* ------------------------------------------------------------------ */
|
|
62
|
+
/* ffprobe */
|
|
63
|
+
/* ------------------------------------------------------------------ */
|
|
64
|
+
|
|
65
|
+
export interface ProbeResult {
|
|
66
|
+
format: {
|
|
67
|
+
duration?: string;
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
};
|
|
70
|
+
streams: Array<{
|
|
71
|
+
codec_type?: string;
|
|
72
|
+
codec_name?: string;
|
|
73
|
+
width?: number;
|
|
74
|
+
height?: number;
|
|
75
|
+
[key: string]: unknown;
|
|
76
|
+
}>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Probe a media file for metadata using ffprobe.
|
|
81
|
+
*/
|
|
82
|
+
export function ffprobe(filePath: string): Promise<ProbeResult> {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const args = [
|
|
85
|
+
'-v',
|
|
86
|
+
'quiet',
|
|
87
|
+
'-print_format',
|
|
88
|
+
'json',
|
|
89
|
+
'-show_format',
|
|
90
|
+
'-show_streams',
|
|
91
|
+
filePath,
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const proc = spawn(resolveFfprobePath(), args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
95
|
+
const chunks: Uint8Array[] = [];
|
|
96
|
+
let stderr = '';
|
|
97
|
+
|
|
98
|
+
proc.stdout.on('data', (data: Uint8Array) => chunks.push(data));
|
|
99
|
+
proc.stderr.on('data', (data: Uint8Array) => {
|
|
100
|
+
stderr += data.toString();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
proc.on('close', (code: number | null) => {
|
|
104
|
+
if (code !== 0) {
|
|
105
|
+
return reject(new Error(`ffprobe exited with code ${code}: ${stderr}`));
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const json = JSON.parse(Buffer.concat(chunks).toString());
|
|
109
|
+
resolve(json as ProbeResult);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
reject(new Error(`Failed to parse ffprobe output: ${err}`));
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
proc.on('error', reject);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ------------------------------------------------------------------ */
|
|
120
|
+
/* ffmpeg runner */
|
|
121
|
+
/* ------------------------------------------------------------------ */
|
|
122
|
+
|
|
123
|
+
export interface FfmpegRunOptions {
|
|
124
|
+
args: string[];
|
|
125
|
+
/** Parse progress from stderr lines and call back with percent 0–1. */
|
|
126
|
+
onProgress?: (percent: number) => void;
|
|
127
|
+
/** Parse stderr lines (e.g. for showinfo timestamps). */
|
|
128
|
+
onStderr?: (line: string) => void;
|
|
129
|
+
/** Total duration in seconds — used to compute progress percentage. */
|
|
130
|
+
durationSecs?: number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Run an ffmpeg command and return a promise that resolves on success.
|
|
135
|
+
*/
|
|
136
|
+
export function runFfmpeg(options: FfmpegRunOptions): Promise<void> {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const args = ['-y', ...options.args];
|
|
139
|
+
const proc = spawn(resolveFfmpegPath(), args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
|
140
|
+
let stderrBuf = '';
|
|
141
|
+
|
|
142
|
+
proc.stderr.on('data', (data: Uint8Array) => {
|
|
143
|
+
stderrBuf += data.toString();
|
|
144
|
+
|
|
145
|
+
// Process line by line
|
|
146
|
+
const lines = stderrBuf.split('\n');
|
|
147
|
+
stderrBuf = lines.pop() ?? '';
|
|
148
|
+
|
|
149
|
+
for (const line of lines) {
|
|
150
|
+
options.onStderr?.(line);
|
|
151
|
+
|
|
152
|
+
// Parse progress: "time=HH:MM:SS.ms" or "time=SS.ms"
|
|
153
|
+
if (options.onProgress && options.durationSecs && options.durationSecs > 0) {
|
|
154
|
+
const timeMatch = line.match(/time=(\d+):(\d+):(\d+)\.(\d+)/);
|
|
155
|
+
if (timeMatch) {
|
|
156
|
+
const secs =
|
|
157
|
+
parseInt(timeMatch[1], 10) * 3600 +
|
|
158
|
+
parseInt(timeMatch[2], 10) * 60 +
|
|
159
|
+
parseInt(timeMatch[3], 10) +
|
|
160
|
+
parseInt(timeMatch[4], 10) / 100;
|
|
161
|
+
options.onProgress(Math.min(1, secs / options.durationSecs));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
proc.on('close', (code: number | null) => {
|
|
168
|
+
if (code !== 0) {
|
|
169
|
+
return reject(new Error(`ffmpeg exited with code ${code}`));
|
|
170
|
+
}
|
|
171
|
+
resolve();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
proc.on('error', reject);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
@@ -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
|
|
|
4
5
|
export interface VideoProcessResult {
|
|
5
6
|
audioPath?: string;
|
|
@@ -273,13 +274,11 @@ export async function processVideo(
|
|
|
273
274
|
videoPath: string,
|
|
274
275
|
options: VideoProcessOptions,
|
|
275
276
|
): Promise<VideoProcessResult> {
|
|
276
|
-
const ffmpeg = await importFfmpeg();
|
|
277
|
-
|
|
278
277
|
await fs.mkdir(options.outputDir, { recursive: true });
|
|
279
278
|
const framesDir = path.join(options.outputDir, 'frames');
|
|
280
279
|
await fs.mkdir(framesDir, { recursive: true });
|
|
281
280
|
|
|
282
|
-
const meta = await probeVideo(
|
|
281
|
+
const meta = await probeVideo(videoPath);
|
|
283
282
|
|
|
284
283
|
const audioPath = path.join(options.outputDir, 'audio.wav');
|
|
285
284
|
const interval = options.frameIntervalSecs ?? 10;
|
|
@@ -294,7 +293,7 @@ export async function processVideo(
|
|
|
294
293
|
|
|
295
294
|
const extractAudioPromise = options.skipAudioExtraction
|
|
296
295
|
? Promise.resolve(undefined)
|
|
297
|
-
: extractAudio(
|
|
296
|
+
: extractAudio(videoPath, audioPath, options.threads, (progress) => {
|
|
298
297
|
audioProgress = Math.max(audioProgress, progress);
|
|
299
298
|
reportExtractionProgress();
|
|
300
299
|
})
|
|
@@ -345,9 +344,8 @@ export async function extractSceneFrames(
|
|
|
345
344
|
onProgress?: (progress: number) => void;
|
|
346
345
|
},
|
|
347
346
|
): Promise<{ path: string; timestampSecs: number }[]> {
|
|
348
|
-
const ffmpeg = await importFfmpeg();
|
|
349
347
|
await fs.mkdir(options.outputDir, { recursive: true });
|
|
350
|
-
const duration = options.durationSecs || (await probeVideo(
|
|
348
|
+
const duration = options.durationSecs || (await probeVideo(videoPath)).durationSecs;
|
|
351
349
|
const plan = createVideoSamplingPlan(duration, options.intervalSecs, options.maxFrames);
|
|
352
350
|
const totalPlannedWork = Math.max(1, plan.estimatedFrameCount);
|
|
353
351
|
const periodicWeight = plan.periodicFrameCount / totalPlannedWork;
|
|
@@ -399,7 +397,6 @@ export async function extractSceneFrames(
|
|
|
399
397
|
? []
|
|
400
398
|
: duration >= 2 * 60 * 60
|
|
401
399
|
? await extractFramesAtTimestamps(
|
|
402
|
-
ffmpeg,
|
|
403
400
|
videoPath,
|
|
404
401
|
Array.from({ length: plan.periodicFrameCount }, (_, index) =>
|
|
405
402
|
Math.min(Math.max(0, duration - 1), index * plan.periodicIntervalSecs),
|
|
@@ -425,39 +422,38 @@ export async function extractSceneFrames(
|
|
|
425
422
|
await fs.mkdir(sceneDir, { recursive: true });
|
|
426
423
|
const pattern = path.join(sceneDir, 'scene_%04d.jpg');
|
|
427
424
|
let timestamps: number[] = [];
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
425
|
+
const isLongRecording = duration >= 2 * 60 * 60;
|
|
426
|
+
const vf = isLongRecording
|
|
427
|
+
? `scale='min(640,iw)':-2,select='gt(scene,${threshold})*if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${plan.minimumSceneGapSecs}))',showinfo`
|
|
428
|
+
: `select='gt(scene,${threshold})*if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${plan.minimumSceneGapSecs}))',showinfo,scale='min(1280,iw)':-2`;
|
|
429
|
+
const sceneArgs = [
|
|
430
|
+
...(isLongRecording ? ['-skip_frame', 'nokey'] : []),
|
|
431
|
+
'-i',
|
|
432
|
+
videoPath,
|
|
433
|
+
'-vf',
|
|
434
|
+
vf,
|
|
435
|
+
'-vsync',
|
|
436
|
+
'vfr',
|
|
437
|
+
'-frames:v',
|
|
438
|
+
String(plan.sceneFrameCount),
|
|
439
|
+
...(options.threads ? ['-threads', String(options.threads)] : []),
|
|
440
|
+
pattern,
|
|
441
|
+
];
|
|
442
|
+
try {
|
|
443
|
+
await runFfmpeg({
|
|
444
|
+
args: sceneArgs,
|
|
445
|
+
durationSecs: duration,
|
|
446
|
+
onProgress: (percent) => {
|
|
447
|
+
reportProgress(periodicWeight + percent * sceneWeight);
|
|
448
|
+
},
|
|
449
|
+
onStderr: (line) => {
|
|
447
450
|
const match = line.match(/pts_time:([0-9.]+)/);
|
|
448
451
|
if (match) timestamps.push(Number(match[1]));
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
})
|
|
455
|
-
.on('end', () => resolve())
|
|
456
|
-
.on('error', () => {
|
|
457
|
-
resolve();
|
|
458
|
-
})
|
|
459
|
-
.run();
|
|
460
|
-
});
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
} catch {
|
|
455
|
+
// Scene detection may fail on some codecs — continue with periodic frames
|
|
456
|
+
}
|
|
461
457
|
reportProgress(periodicWeight + sceneWeight);
|
|
462
458
|
const files = (await fs.readdir(sceneDir))
|
|
463
459
|
.filter((name) => /^scene_\d+\.jpg$/.test(name))
|
|
@@ -481,7 +477,6 @@ export async function extractSceneFrames(
|
|
|
481
477
|
}
|
|
482
478
|
|
|
483
479
|
async function extractFramesAtTimestamps(
|
|
484
|
-
ffmpeg: Awaited<ReturnType<typeof importFfmpeg>>,
|
|
485
480
|
videoPath: string,
|
|
486
481
|
timestamps: number[],
|
|
487
482
|
options: {
|
|
@@ -504,19 +499,19 @@ async function extractFramesAtTimestamps(
|
|
|
504
499
|
options.outputDir,
|
|
505
500
|
`frame_${String(index + 1).padStart(4, '0')}.jpg`,
|
|
506
501
|
);
|
|
507
|
-
await
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
.
|
|
518
|
-
|
|
519
|
-
|
|
502
|
+
await runFfmpeg({
|
|
503
|
+
args: [
|
|
504
|
+
'-ss',
|
|
505
|
+
String(timestampSecs),
|
|
506
|
+
'-i',
|
|
507
|
+
videoPath,
|
|
508
|
+
'-vf',
|
|
509
|
+
`scale='min(1280,iw)':-2`,
|
|
510
|
+
'-frames:v',
|
|
511
|
+
'1',
|
|
512
|
+
...(options.threads ? ['-threads', String(options.threads)] : []),
|
|
513
|
+
outputPath,
|
|
514
|
+
],
|
|
520
515
|
});
|
|
521
516
|
results[index] = { path: outputPath, timestampSecs };
|
|
522
517
|
completed += 1;
|
|
@@ -540,12 +535,11 @@ export async function extractFrames(
|
|
|
540
535
|
onProgress?: (progress: number) => void;
|
|
541
536
|
},
|
|
542
537
|
): Promise<{ path: string; timestampSecs: number }[]> {
|
|
543
|
-
const ffmpeg = await importFfmpeg();
|
|
544
538
|
await fs.mkdir(options.outputDir, { recursive: true });
|
|
545
539
|
|
|
546
540
|
let duration = options.durationSecs;
|
|
547
541
|
if (!duration) {
|
|
548
|
-
const meta = await probeVideo(
|
|
542
|
+
const meta = await probeVideo(videoPath);
|
|
549
543
|
duration = meta.durationSecs;
|
|
550
544
|
}
|
|
551
545
|
|
|
@@ -556,27 +550,22 @@ export async function extractFrames(
|
|
|
556
550
|
Math.min(Math.ceil(rangeDuration / options.intervalSecs), options.maxFrames),
|
|
557
551
|
);
|
|
558
552
|
const pattern = path.join(options.outputDir, 'frame_%04d.jpg');
|
|
559
|
-
await
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
)
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
])
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
})
|
|
577
|
-
.on('end', () => resolve())
|
|
578
|
-
.on('error', (err: Error) => reject(err))
|
|
579
|
-
.run();
|
|
553
|
+
await runFfmpeg({
|
|
554
|
+
args: [
|
|
555
|
+
...(startSecs > 0 ? ['-ss', String(startSecs)] : []),
|
|
556
|
+
'-i',
|
|
557
|
+
videoPath,
|
|
558
|
+
'-vf',
|
|
559
|
+
`select='if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${options.intervalSecs}))',scale='min(1280,iw)':-2`,
|
|
560
|
+
'-vsync',
|
|
561
|
+
'vfr',
|
|
562
|
+
'-frames:v',
|
|
563
|
+
String(frameCount),
|
|
564
|
+
...(options.threads ? ['-threads', String(options.threads)] : []),
|
|
565
|
+
pattern,
|
|
566
|
+
],
|
|
567
|
+
durationSecs: rangeDuration,
|
|
568
|
+
onProgress: options.onProgress,
|
|
580
569
|
});
|
|
581
570
|
const files = (await fs.readdir(options.outputDir))
|
|
582
571
|
.filter((name) => /^frame_\d+\.jpg$/.test(name))
|
|
@@ -588,40 +577,27 @@ export async function extractFrames(
|
|
|
588
577
|
}));
|
|
589
578
|
}
|
|
590
579
|
|
|
591
|
-
async function importFfmpeg() {
|
|
592
|
-
const mod = await import('fluent-ffmpeg');
|
|
593
|
-
return mod;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
580
|
function extractAudio(
|
|
597
|
-
ffmpeg: any,
|
|
598
581
|
videoPath: string,
|
|
599
582
|
outputPath: string,
|
|
600
583
|
threads?: number,
|
|
601
584
|
onProgress?: (progress: number) => void,
|
|
602
585
|
): Promise<void> {
|
|
603
|
-
return
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
if (typeof progress.percent === 'number') {
|
|
619
|
-
onProgress?.(Math.min(1, progress.percent / 100));
|
|
620
|
-
}
|
|
621
|
-
})
|
|
622
|
-
.on('end', () => resolve())
|
|
623
|
-
.on('error', (err: Error) => reject(err))
|
|
624
|
-
.run();
|
|
586
|
+
return runFfmpeg({
|
|
587
|
+
args: [
|
|
588
|
+
'-i',
|
|
589
|
+
videoPath,
|
|
590
|
+
'-vn',
|
|
591
|
+
'-ac',
|
|
592
|
+
'1',
|
|
593
|
+
'-ar',
|
|
594
|
+
'16000',
|
|
595
|
+
'-f',
|
|
596
|
+
'wav',
|
|
597
|
+
...(threads ? ['-threads', String(threads)] : []),
|
|
598
|
+
outputPath,
|
|
599
|
+
],
|
|
600
|
+
onProgress,
|
|
625
601
|
});
|
|
626
602
|
}
|
|
627
603
|
|
|
@@ -632,17 +608,13 @@ interface VideoMeta {
|
|
|
632
608
|
codec: string;
|
|
633
609
|
}
|
|
634
610
|
|
|
635
|
-
function probeVideo(
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
codec: videoStream?.codec_name ?? 'unknown',
|
|
645
|
-
});
|
|
646
|
-
});
|
|
647
|
-
});
|
|
611
|
+
async function probeVideo(videoPath: string): Promise<VideoMeta> {
|
|
612
|
+
const data = await ffprobe(videoPath);
|
|
613
|
+
const videoStream = data.streams?.find((s) => s.codec_type === 'video');
|
|
614
|
+
return {
|
|
615
|
+
durationSecs: parseFloat(data.format?.duration ?? '0'),
|
|
616
|
+
width: videoStream?.width ?? 0,
|
|
617
|
+
height: videoStream?.height ?? 0,
|
|
618
|
+
codec: videoStream?.codec_name ?? 'unknown',
|
|
619
|
+
};
|
|
648
620
|
}
|