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.
Files changed (241) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/diagnostics/route-bundle-stats.json +12 -12
  4. package/.next/fallback-build-manifest.json +3 -3
  5. package/.next/required-server-files.js +1 -1
  6. package/.next/required-server-files.json +1 -1
  7. package/.next/server/app/_global-error.html +1 -1
  8. package/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/.next/server/app/_not-found.html +1 -1
  16. package/.next/server/app/_not-found.rsc +16 -16
  17. package/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
  18. package/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  19. package/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
  20. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  21. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  23. package/.next/server/app/analytics/page_client-reference-manifest.js +1 -1
  24. package/.next/server/app/analytics.html +1 -1
  25. package/.next/server/app/analytics.rsc +17 -17
  26. package/.next/server/app/analytics.segments/_full.segment.rsc +17 -17
  27. package/.next/server/app/analytics.segments/_head.segment.rsc +4 -4
  28. package/.next/server/app/analytics.segments/_index.segment.rsc +11 -11
  29. package/.next/server/app/analytics.segments/_tree.segment.rsc +1 -1
  30. package/.next/server/app/analytics.segments/analytics/__PAGE__.segment.rsc +3 -3
  31. package/.next/server/app/analytics.segments/analytics.segment.rsc +3 -3
  32. package/.next/server/app/api/chat/route.js +9 -9
  33. package/.next/server/app/api/chat/route.js.nft.json +1 -1
  34. package/.next/server/app/api/deploy/ssh/route.js +2 -2
  35. package/.next/server/app/api/deploy/ssh/route.js.nft.json +1 -1
  36. package/.next/server/app/api/doc-editor/edit/route.js +2 -2
  37. package/.next/server/app/api/doc-editor/export/route.js +2 -2
  38. package/.next/server/app/api/doc-editor/extract-text/route.js +2 -2
  39. package/.next/server/app/api/doc-editor/parse/route.js +1 -1
  40. package/.next/server/app/api/doc-editor/restore/route.js +2 -2
  41. package/.next/server/app/api/document/sign/route.js +3 -3
  42. package/.next/server/app/api/marketplace/[toolId]/route.js.nft.json +1 -1
  43. package/.next/server/app/api/marketplace/route.js.nft.json +1 -1
  44. package/.next/server/app/api/media/process/route.js.nft.json +1 -1
  45. package/.next/server/app/api/media/route.js.nft.json +1 -1
  46. package/.next/server/app/api/parse-file/route.js +2 -2
  47. package/.next/server/app/api/server/download/route.js +1 -1
  48. package/.next/server/app/api/server/generate/route.js +2 -2
  49. package/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  50. package/.next/server/app/chat.html +1 -1
  51. package/.next/server/app/chat.rsc +17 -17
  52. package/.next/server/app/chat.segments/_full.segment.rsc +17 -17
  53. package/.next/server/app/chat.segments/_head.segment.rsc +4 -4
  54. package/.next/server/app/chat.segments/_index.segment.rsc +11 -11
  55. package/.next/server/app/chat.segments/_tree.segment.rsc +1 -1
  56. package/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +3 -3
  57. package/.next/server/app/chat.segments/chat.segment.rsc +3 -3
  58. package/.next/server/app/data/page_client-reference-manifest.js +1 -1
  59. package/.next/server/app/data.html +1 -1
  60. package/.next/server/app/data.rsc +17 -17
  61. package/.next/server/app/data.segments/_full.segment.rsc +17 -17
  62. package/.next/server/app/data.segments/_head.segment.rsc +4 -4
  63. package/.next/server/app/data.segments/_index.segment.rsc +11 -11
  64. package/.next/server/app/data.segments/_tree.segment.rsc +1 -1
  65. package/.next/server/app/data.segments/data/__PAGE__.segment.rsc +3 -3
  66. package/.next/server/app/data.segments/data.segment.rsc +3 -3
  67. package/.next/server/app/index.html +1 -1
  68. package/.next/server/app/index.rsc +16 -16
  69. package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  70. package/.next/server/app/index.segments/_full.segment.rsc +16 -16
  71. package/.next/server/app/index.segments/_head.segment.rsc +4 -4
  72. package/.next/server/app/index.segments/_index.segment.rsc +11 -11
  73. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  74. package/.next/server/app/page_client-reference-manifest.js +1 -1
  75. package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  76. package/.next/server/app/settings.html +1 -1
  77. package/.next/server/app/settings.rsc +18 -18
  78. package/.next/server/app/settings.segments/_full.segment.rsc +18 -18
  79. package/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  80. package/.next/server/app/settings.segments/_index.segment.rsc +11 -11
  81. package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  82. package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  83. package/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  84. package/.next/server/chunks/[root-of-the-server]__05707i3._.js +1 -1
  85. package/.next/server/chunks/[root-of-the-server]__0vmcld0._.js +3 -0
  86. package/.next/{standalone/apps/web/.next/server/chunks/[root-of-the-server]__08f60c7._.js → server/chunks/[root-of-the-server]__1e8p5bd._.js} +2 -2
  87. package/.next/server/chunks/[root-of-the-server]__1ks-3_h._.js +3 -0
  88. package/.next/server/chunks/[root-of-the-server]__1lhfkyh._.js +1 -1
  89. package/.next/server/chunks/packages_marketplace_src_125_g2p._.js +1 -1
  90. package/.next/server/chunks/packages_scraper_src_0kkcv_v._.js +1 -1
  91. package/.next/server/chunks/packages_tools_video-audio_dist_ffmpeg-spawn_0by2bau.js +3 -0
  92. package/.next/server/chunks/ssr/_1yqlx1m._.js +1 -1
  93. package/.next/server/middleware-build-manifest.js +3 -3
  94. package/.next/server/pages/404.html +1 -1
  95. package/.next/server/pages/500.html +1 -1
  96. package/.next/standalone/apps/web/.next/BUILD_ID +1 -1
  97. package/.next/standalone/apps/web/.next/build-manifest.json +3 -3
  98. package/.next/standalone/apps/web/.next/required-server-files.json +1 -1
  99. package/.next/standalone/apps/web/.next/server/app/_global-error.html +1 -1
  100. package/.next/standalone/apps/web/.next/server/app/_global-error.rsc +1 -1
  101. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  102. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  103. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  104. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  105. package/.next/standalone/apps/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  106. package/.next/standalone/apps/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  107. package/.next/standalone/apps/web/.next/server/app/_not-found.html +1 -1
  108. package/.next/standalone/apps/web/.next/server/app/_not-found.rsc +16 -16
  109. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
  110. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  111. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_index.segment.rsc +11 -11
  112. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  113. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  114. package/.next/standalone/apps/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  115. package/.next/standalone/apps/web/.next/server/app/analytics/page_client-reference-manifest.js +1 -1
  116. package/.next/standalone/apps/web/.next/server/app/analytics.html +1 -1
  117. package/.next/standalone/apps/web/.next/server/app/analytics.rsc +17 -17
  118. package/.next/standalone/apps/web/.next/server/app/analytics.segments/_full.segment.rsc +17 -17
  119. package/.next/standalone/apps/web/.next/server/app/analytics.segments/_head.segment.rsc +4 -4
  120. package/.next/standalone/apps/web/.next/server/app/analytics.segments/_index.segment.rsc +11 -11
  121. package/.next/standalone/apps/web/.next/server/app/analytics.segments/_tree.segment.rsc +1 -1
  122. package/.next/standalone/apps/web/.next/server/app/analytics.segments/analytics/__PAGE__.segment.rsc +3 -3
  123. package/.next/standalone/apps/web/.next/server/app/analytics.segments/analytics.segment.rsc +3 -3
  124. package/.next/standalone/apps/web/.next/server/app/api/chat/route.js +9 -9
  125. package/.next/standalone/apps/web/.next/server/app/api/chat/route.js.nft.json +1 -1
  126. package/.next/standalone/apps/web/.next/server/app/api/deploy/ssh/route.js +2 -2
  127. package/.next/standalone/apps/web/.next/server/app/api/deploy/ssh/route.js.nft.json +1 -1
  128. package/.next/standalone/apps/web/.next/server/app/api/doc-editor/edit/route.js +2 -2
  129. package/.next/standalone/apps/web/.next/server/app/api/doc-editor/export/route.js +2 -2
  130. package/.next/standalone/apps/web/.next/server/app/api/doc-editor/extract-text/route.js +2 -2
  131. package/.next/standalone/apps/web/.next/server/app/api/doc-editor/parse/route.js +1 -1
  132. package/.next/standalone/apps/web/.next/server/app/api/doc-editor/restore/route.js +2 -2
  133. package/.next/standalone/apps/web/.next/server/app/api/document/sign/route.js +3 -3
  134. package/.next/standalone/apps/web/.next/server/app/api/marketplace/[toolId]/route.js.nft.json +1 -1
  135. package/.next/standalone/apps/web/.next/server/app/api/marketplace/route.js.nft.json +1 -1
  136. package/.next/standalone/apps/web/.next/server/app/api/media/process/route.js.nft.json +1 -1
  137. package/.next/standalone/apps/web/.next/server/app/api/media/route.js.nft.json +1 -1
  138. package/.next/standalone/apps/web/.next/server/app/api/parse-file/route.js +2 -2
  139. package/.next/standalone/apps/web/.next/server/app/api/server/download/route.js +1 -1
  140. package/.next/standalone/apps/web/.next/server/app/api/server/generate/route.js +2 -2
  141. package/.next/standalone/apps/web/.next/server/app/chat/page_client-reference-manifest.js +1 -1
  142. package/.next/standalone/apps/web/.next/server/app/chat.html +1 -1
  143. package/.next/standalone/apps/web/.next/server/app/chat.rsc +17 -17
  144. package/.next/standalone/apps/web/.next/server/app/chat.segments/_full.segment.rsc +17 -17
  145. package/.next/standalone/apps/web/.next/server/app/chat.segments/_head.segment.rsc +4 -4
  146. package/.next/standalone/apps/web/.next/server/app/chat.segments/_index.segment.rsc +11 -11
  147. package/.next/standalone/apps/web/.next/server/app/chat.segments/_tree.segment.rsc +1 -1
  148. package/.next/standalone/apps/web/.next/server/app/chat.segments/chat/__PAGE__.segment.rsc +3 -3
  149. package/.next/standalone/apps/web/.next/server/app/chat.segments/chat.segment.rsc +3 -3
  150. package/.next/standalone/apps/web/.next/server/app/data/page_client-reference-manifest.js +1 -1
  151. package/.next/standalone/apps/web/.next/server/app/data.html +1 -1
  152. package/.next/standalone/apps/web/.next/server/app/data.rsc +17 -17
  153. package/.next/standalone/apps/web/.next/server/app/data.segments/_full.segment.rsc +17 -17
  154. package/.next/standalone/apps/web/.next/server/app/data.segments/_head.segment.rsc +4 -4
  155. package/.next/standalone/apps/web/.next/server/app/data.segments/_index.segment.rsc +11 -11
  156. package/.next/standalone/apps/web/.next/server/app/data.segments/_tree.segment.rsc +1 -1
  157. package/.next/standalone/apps/web/.next/server/app/data.segments/data/__PAGE__.segment.rsc +3 -3
  158. package/.next/standalone/apps/web/.next/server/app/data.segments/data.segment.rsc +3 -3
  159. package/.next/standalone/apps/web/.next/server/app/index.html +1 -1
  160. package/.next/standalone/apps/web/.next/server/app/index.rsc +16 -16
  161. package/.next/standalone/apps/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  162. package/.next/standalone/apps/web/.next/server/app/index.segments/_full.segment.rsc +16 -16
  163. package/.next/standalone/apps/web/.next/server/app/index.segments/_head.segment.rsc +4 -4
  164. package/.next/standalone/apps/web/.next/server/app/index.segments/_index.segment.rsc +11 -11
  165. package/.next/standalone/apps/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  166. package/.next/standalone/apps/web/.next/server/app/page_client-reference-manifest.js +1 -1
  167. package/.next/standalone/apps/web/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  168. package/.next/standalone/apps/web/.next/server/app/settings.html +1 -1
  169. package/.next/standalone/apps/web/.next/server/app/settings.rsc +18 -18
  170. package/.next/standalone/apps/web/.next/server/app/settings.segments/_full.segment.rsc +18 -18
  171. package/.next/standalone/apps/web/.next/server/app/settings.segments/_head.segment.rsc +4 -4
  172. package/.next/standalone/apps/web/.next/server/app/settings.segments/_index.segment.rsc +11 -11
  173. package/.next/standalone/apps/web/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
  174. package/.next/standalone/apps/web/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +4 -4
  175. package/.next/standalone/apps/web/.next/server/app/settings.segments/settings.segment.rsc +3 -3
  176. package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__05707i3._.js +1 -1
  177. package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__0vmcld0._.js +3 -0
  178. package/.next/{server/chunks/[root-of-the-server]__08f60c7._.js → standalone/apps/web/.next/server/chunks/[root-of-the-server]__1e8p5bd._.js} +2 -2
  179. package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__1ks-3_h._.js +3 -0
  180. package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__1lhfkyh._.js +1 -1
  181. package/.next/standalone/apps/web/.next/server/chunks/packages_marketplace_src_125_g2p._.js +1 -1
  182. package/.next/standalone/apps/web/.next/server/chunks/packages_scraper_src_0kkcv_v._.js +1 -1
  183. package/.next/standalone/apps/web/.next/server/chunks/packages_tools_video-audio_dist_ffmpeg-spawn_0by2bau.js +3 -0
  184. package/.next/standalone/apps/web/.next/server/chunks/ssr/_1yqlx1m._.js +1 -1
  185. package/.next/standalone/apps/web/.next/server/middleware-build-manifest.js +3 -3
  186. package/.next/standalone/apps/web/.next/server/pages/404.html +1 -1
  187. package/.next/standalone/apps/web/.next/server/pages/500.html +1 -1
  188. package/.next/standalone/apps/web/.next/static/chunks/{041vy184s-7sh.js → 2azppfcsqga3r.js} +1 -1
  189. package/.next/standalone/apps/web/CHANGELOG.md +18 -0
  190. package/.next/standalone/apps/web/components/update-banner.tsx +2 -2
  191. package/.next/standalone/apps/web/package.json +1 -1
  192. package/.next/standalone/apps/web/server.js +1 -1
  193. package/.next/standalone/packages/tools/video-audio/CHANGELOG.md +6 -0
  194. package/.next/standalone/packages/tools/video-audio/dist/audio-processor.js +30 -32
  195. package/.next/standalone/packages/tools/video-audio/dist/ffmpeg-spawn.js +127 -0
  196. package/.next/standalone/packages/tools/video-audio/dist/video-processor.js +95 -113
  197. package/.next/standalone/packages/tools/video-audio/package.json +2 -7
  198. package/.next/standalone/packages/tools/video-audio/src/audio-processor.ts +30 -31
  199. package/.next/standalone/packages/tools/video-audio/src/ffmpeg-spawn.ts +176 -0
  200. package/.next/standalone/packages/tools/video-audio/src/video-processor.ts +88 -116
  201. package/.next/static/chunks/{041vy184s-7sh.js → 2azppfcsqga3r.js} +1 -1
  202. package/CHANGELOG.md +18 -0
  203. package/LICENSE +176 -0
  204. package/components/update-banner.tsx +2 -2
  205. package/package.json +6 -6
  206. package/.next/server/chunks/[externals]__1_tvo45._.js +0 -3
  207. package/.next/server/chunks/[root-of-the-server]__010rwkn._.js +0 -3
  208. package/.next/server/chunks/[root-of-the-server]__0xbvdcw._.js +0 -3
  209. package/.next/server/chunks/node_modules__pnpm_0ddqx71._.js +0 -3
  210. package/.next/standalone/apps/web/.next/server/chunks/[externals]__1_tvo45._.js +0 -3
  211. package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__010rwkn._.js +0 -3
  212. package/.next/standalone/apps/web/.next/server/chunks/[root-of-the-server]__0xbvdcw._.js +0 -3
  213. package/.next/standalone/apps/web/.next/server/chunks/node_modules__pnpm_0ddqx71._.js +0 -3
  214. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/capabilities.js +0 -665
  215. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/ffprobe.js +0 -261
  216. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/fluent-ffmpeg.js +0 -226
  217. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/audio.js +0 -178
  218. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/custom.js +0 -212
  219. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/inputs.js +0 -178
  220. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/misc.js +0 -41
  221. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/output.js +0 -162
  222. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/video.js +0 -184
  223. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/options/videosize.js +0 -291
  224. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/presets/divx.js +0 -14
  225. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/presets/flashvideo.js +0 -16
  226. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/presets/podcast.js +0 -16
  227. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/processor.js +0 -662
  228. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/recipes.js +0 -456
  229. package/.next/standalone/node_modules/.pnpm/fluent-ffmpeg@2.1.3/node_modules/fluent-ffmpeg/lib/utils.js +0 -455
  230. package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/CHANGELOG.md +0 -152
  231. package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/LICENSE +0 -15
  232. package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/README.md +0 -51
  233. package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/bin/which +0 -52
  234. package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/package.json +0 -30
  235. package/.next/standalone/node_modules/.pnpm/which@1.3.1/node_modules/which/which.js +0 -135
  236. /package/.next/standalone/apps/web/.next/static/{IHAOZOY0iJJ_cMmT7-lqT → PM7-3u8cSkShjD80zaJ4l}/_buildManifest.js +0 -0
  237. /package/.next/standalone/apps/web/.next/static/{IHAOZOY0iJJ_cMmT7-lqT → PM7-3u8cSkShjD80zaJ4l}/_clientMiddlewareManifest.js +0 -0
  238. /package/.next/standalone/apps/web/.next/static/{IHAOZOY0iJJ_cMmT7-lqT → PM7-3u8cSkShjD80zaJ4l}/_ssgManifest.js +0 -0
  239. /package/.next/static/{IHAOZOY0iJJ_cMmT7-lqT → PM7-3u8cSkShjD80zaJ4l}/_buildManifest.js +0 -0
  240. /package/.next/static/{IHAOZOY0iJJ_cMmT7-lqT → PM7-3u8cSkShjD80zaJ4l}/_clientMiddlewareManifest.js +0 -0
  241. /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(ffmpeg, videoPath);
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(ffmpeg, videoPath, audioPath, options.threads, (progress) => {
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(ffmpeg, videoPath)).durationSecs;
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
- await new Promise<void>((resolve) => {
429
- const command = ffmpeg.default(videoPath);
430
- const isLongRecording = duration >= 2 * 60 * 60;
431
- if (isLongRecording) {
432
- command.inputOptions(['-skip_frame nokey']);
433
- }
434
- command
435
- .videoFilters(
436
- isLongRecording
437
- ? `scale='min(640,iw)':-2,select='gt(scene,${threshold})*if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${plan.minimumSceneGapSecs}))',showinfo`
438
- : `select='gt(scene,${threshold})*if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${plan.minimumSceneGapSecs}))',showinfo,scale='min(1280,iw)':-2`,
439
- )
440
- .outputOptions([
441
- '-vsync vfr',
442
- `-frames:v ${plan.sceneFrameCount}`,
443
- ...(options.threads ? [`-threads ${options.threads}`] : []),
444
- ])
445
- .output(pattern)
446
- .on('stderr', (line: string) => {
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
- .on('progress', (progress: { percent?: number }) => {
451
- if (typeof progress.percent === 'number') {
452
- reportProgress(periodicWeight + Math.min(1, progress.percent / 100) * sceneWeight);
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 new Promise<void>((resolve, reject) => {
508
- ffmpeg
509
- .default(videoPath)
510
- .seekInput(timestampSecs)
511
- .videoFilters(`scale='min(1280,iw)':-2`)
512
- .outputOptions([
513
- '-frames:v 1',
514
- ...(options.threads ? [`-threads ${options.threads}`] : []),
515
- ])
516
- .output(outputPath)
517
- .on('end', () => resolve())
518
- .on('error', (error: Error) => reject(error))
519
- .run();
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(ffmpeg, videoPath);
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 new Promise<void>((resolve, reject) => {
560
- const command = ffmpeg.default(videoPath);
561
- if (startSecs > 0) command.seekInput(startSecs);
562
- command
563
- .videoFilters(
564
- `select='if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${options.intervalSecs}))',scale='min(1280,iw)':-2`,
565
- )
566
- .outputOptions([
567
- '-vsync vfr',
568
- `-frames:v ${frameCount}`,
569
- ...(options.threads ? [`-threads ${options.threads}`] : []),
570
- ])
571
- .output(pattern)
572
- .on('progress', (progress: { percent?: number }) => {
573
- if (typeof progress.percent === 'number') {
574
- options.onProgress?.(Math.min(1, progress.percent / 100));
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 new Promise((resolve, reject) => {
604
- let cmd = ffmpeg
605
- .default(videoPath)
606
- .noVideo()
607
- .audioChannels(1)
608
- .audioFrequency(16000)
609
- .format('wav');
610
-
611
- if (threads) {
612
- cmd = cmd.outputOptions([`-threads ${threads}`]);
613
- }
614
-
615
- cmd
616
- .output(outputPath)
617
- .on('progress', (progress: { percent?: number }) => {
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(ffmpeg: any, videoPath: string): Promise<VideoMeta> {
636
- return new Promise((resolve, reject) => {
637
- ffmpeg.default.ffprobe(videoPath, (err: Error | null, data: any) => {
638
- if (err) return reject(err);
639
- const videoStream = data.streams?.find((s: any) => s.codec_type === 'video');
640
- resolve({
641
- durationSecs: parseFloat(data.format?.duration ?? '0'),
642
- width: videoStream?.width ?? 0,
643
- height: videoStream?.height ?? 0,
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
  }