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,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(ffmpeg, videoPath);
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(ffmpeg, videoPath, audioPath, options.threads, (progress) => {
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(ffmpeg, videoPath)).durationSecs;
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(ffmpeg, videoPath, Array.from({ length: plan.periodicFrameCount }, (_, index) => Math.min(Math.max(0, duration - 1), index * plan.periodicIntervalSecs)), {
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
- await new Promise((resolve) => {
298
- const command = ffmpeg.default(videoPath);
299
- const isLongRecording = duration >= 2 * 60 * 60;
300
- if (isLongRecording) {
301
- command.inputOptions(['-skip_frame nokey']);
302
- }
303
- command
304
- .videoFilters(isLongRecording
305
- ? `scale='min(640,iw)':-2,select='gt(scene,${threshold})*if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${plan.minimumSceneGapSecs}))',showinfo`
306
- : `select='gt(scene,${threshold})*if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${plan.minimumSceneGapSecs}))',showinfo,scale='min(1280,iw)':-2`)
307
- .outputOptions([
308
- '-vsync vfr',
309
- `-frames:v ${plan.sceneFrameCount}`,
310
- ...(options.threads ? [`-threads ${options.threads}`] : []),
311
- ])
312
- .output(pattern)
313
- .on('stderr', (line) => {
314
- const match = line.match(/pts_time:([0-9.]+)/);
315
- if (match)
316
- timestamps.push(Number(match[1]));
317
- })
318
- .on('progress', (progress) => {
319
- if (typeof progress.percent === 'number') {
320
- reportProgress(periodicWeight + Math.min(1, progress.percent / 100) * sceneWeight);
321
- }
322
- })
323
- .on('end', () => resolve())
324
- .on('error', () => {
325
- resolve();
326
- })
327
- .run();
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(ffmpeg, videoPath, timestamps, options) {
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 new Promise((resolve, reject) => {
356
- ffmpeg
357
- .default(videoPath)
358
- .seekInput(timestampSecs)
359
- .videoFilters(`scale='min(1280,iw)':-2`)
360
- .outputOptions([
361
- '-frames:v 1',
362
- ...(options.threads ? [`-threads ${options.threads}`] : []),
363
- ])
364
- .output(outputPath)
365
- .on('end', () => resolve())
366
- .on('error', (error) => reject(error))
367
- .run();
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(ffmpeg, videoPath);
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 new Promise((resolve, reject) => {
389
- const command = ffmpeg.default(videoPath);
390
- if (startSecs > 0)
391
- command.seekInput(startSecs);
392
- command
393
- .videoFilters(`select='if(isnan(prev_selected_t),1,gte(t-prev_selected_t,${options.intervalSecs}))',scale='min(1280,iw)':-2`)
394
- .outputOptions([
395
- '-vsync vfr',
396
- `-frames:v ${frameCount}`,
397
- ...(options.threads ? [`-threads ${options.threads}`] : []),
398
- ])
399
- .output(pattern)
400
- .on('progress', (progress) => {
401
- if (typeof progress.percent === 'number') {
402
- options.onProgress?.(Math.min(1, progress.percent / 100));
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
- async function importFfmpeg() {
419
- const mod = await import('fluent-ffmpeg');
420
- return mod;
421
- }
422
- function extractAudio(ffmpeg, videoPath, outputPath, threads, onProgress) {
423
- return new Promise((resolve, reject) => {
424
- let cmd = ffmpeg
425
- .default(videoPath)
426
- .noVideo()
427
- .audioChannels(1)
428
- .audioFrequency(16000)
429
- .format('wav');
430
- if (threads) {
431
- cmd = cmd.outputOptions([`-threads ${threads}`]);
432
- }
433
- cmd
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(ffmpeg, videoPath) {
446
- return new Promise((resolve, reject) => {
447
- ffmpeg.default.ffprobe(videoPath, (err, data) => {
448
- if (err)
449
- return reject(err);
450
- const videoStream = data.streams?.find((s) => s.codec_type === 'video');
451
- resolve({
452
- durationSecs: parseFloat(data.format?.duration ?? '0'),
453
- width: videoStream?.width ?? 0,
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.0",
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/fluent-ffmpeg": "^2.1.27",
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 ffmpeg = await importFfmpeg();
604
- return new Promise((resolve, reject) => {
605
- ffmpeg.default.ffprobe(audioPath, (error: Error | null, data: any) => {
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 ffmpeg = await importFfmpeg();
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 new Promise<void>((resolve, reject) => {
624
- ffmpeg
625
- .default(audioPath)
626
- .noVideo()
627
- .audioChannels(1)
628
- .audioFrequency(16_000)
629
- .audioCodec('libmp3lame')
630
- .audioBitrate('32k')
631
- .format('segment')
632
- .outputOptions([`-segment_time ${segmentSecs}`, '-reset_timestamps 1'])
633
- .output(pattern)
634
- .on('progress', (progress: { percent?: number }) => {
635
- if (typeof progress.percent === 'number') {
636
- void onProgress?.(Math.max(0, Math.min(1, progress.percent / 100)));
637
- }
638
- })
639
- .on('end', () => resolve())
640
- .on('error', reject)
641
- .run();
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))