@uploadista/flow-videos-nodes 0.0.19 → 0.0.20-beta.2

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.
@@ -1,22 +1,23 @@
1
1
 
2
2
  
3
- > @uploadista/flow-videos-nodes@0.0.18 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/videos/nodes
3
+ > @uploadista/flow-videos-nodes@0.0.20-beta.1 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/videos/nodes
4
4
  > tsdown
5
5
 
6
- ℹ tsdown v0.16.8 powered by rolldown v1.0.0-beta.52
7
- ℹ Using tsdown config: /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/videos/nodes/tsdown.config.ts
6
+ ℹ tsdown v0.17.1 powered by rolldown v1.0.0-beta.53
7
+ ℹ config file: /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/videos/nodes/tsdown.config.ts
8
8
  ℹ entry: src/index.ts
9
9
  ℹ tsconfig: tsconfig.json
10
10
  ℹ Build start
11
11
  ℹ Cleaning 7 files
12
- ℹ [CJS] dist/index.cjs 4.08 kB │ gzip: 1.28 kB
13
- ℹ [CJS] 1 files, total: 4.08 kB
14
- ℹ [ESM] dist/index.mjs  3.73 kB │ gzip: 1.29 kB
15
- ℹ [ESM] dist/index.mjs.map 18.10 kB │ gzip: 3.72 kB
16
- ℹ [ESM] dist/index.d.mts.map  2.22 kB │ gzip: 0.60 kB
17
- ℹ [ESM] dist/index.d.mts 10.96 kB │ gzip: 1.72 kB
18
- ℹ [ESM] 4 files, total: 35.00 kB
19
- ℹ [CJS] dist/index.d.cts.map  2.22 kB │ gzip: 0.60 kB
20
- ℹ [CJS] dist/index.d.cts 10.96 kB │ gzip: 1.72 kB
21
- ℹ [CJS] 2 files, total: 13.18 kB
22
- ✔ Build complete in 7977ms
12
+ ℹ [CJS] dist/index.cjs 5.29 kB │ gzip: 1.60 kB
13
+ ℹ [CJS] 1 files, total: 5.29 kB
14
+ ℹ [ESM] dist/index.mjs  4.91 kB │ gzip: 1.62 kB
15
+ ℹ [ESM] dist/index.mjs.map 26.96 kB │ gzip: 5.12 kB
16
+ ℹ [ESM] dist/index.d.mts.map  2.51 kB │ gzip: 0.78 kB
17
+ ℹ [ESM] dist/index.d.mts 13.70 kB │ gzip: 2.07 kB
18
+ ℹ [ESM] 4 files, total: 48.08 kB
19
+ ✔ Build complete in 5178ms
20
+ ℹ [CJS] dist/index.d.cts.map  2.51 kB │ gzip: 0.78 kB
21
+ ℹ [CJS] dist/index.d.cts 13.70 kB │ gzip: 2.07 kB
22
+ ℹ [CJS] 2 files, total: 16.21 kB
23
+ ✔ Build complete in 5180ms
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- let e=require(`@uploadista/core/flow`),t=require(`effect`),n=require(`@uploadista/core/errors`);function r(n,r){return t.Effect.gen(function*(){let i=yield*e.VideoPlugin;return yield*(0,e.createTransformNode)({id:n,name:`Describe Video`,description:`Extracts video metadata (duration, resolution, codec, etc.)`,nodeTypeId:`describe-video`,outputTypeId:e.STORAGE_OUTPUT_TYPE_ID,keepOutput:r?.keepOutput,transform:(e,n)=>t.Effect.gen(function*(){let t=yield*i.describe(e);return{bytes:e,metadata:{...n.metadata,videoInfo:t}}})})})}function i(n,r,i){return t.Effect.gen(function*(){let a=yield*e.VideoPlugin,o=i?.naming?{...i.naming,autoSuffix:i.naming.autoSuffix??(e=>`${e.width??r.width}x${e.height??r.height}`)}:void 0;return yield*(0,e.createTransformNode)({id:n,name:`Resize Video`,description:`Changes video resolution`,nodeTypeId:`resize-video`,outputTypeId:e.STORAGE_OUTPUT_TYPE_ID,keepOutput:i?.keepOutput,naming:o,nodeType:`resize-video`,namingVars:{width:r.width,height:r.height},transform:(e,n)=>t.Effect.map(a.resize(e,r),e=>({bytes:e}))})})}function a(n,r,i){return t.Effect.gen(function*(){let a=yield*e.VideoPlugin,o=r.format||`jpeg`;return yield*(0,e.createTransformNode)({id:n,name:`Generate Thumbnail`,description:`Extracts a frame from video as an image`,nodeTypeId:`thumbnail-video`,outputTypeId:e.STORAGE_OUTPUT_TYPE_ID,keepOutput:i?.keepOutput,nodeType:`thumbnail`,namingVars:{format:o},transform:(s,c)=>t.Effect.map(a.extractFrame(s,r),t=>{let r=o===`png`?`image/png`:`image/jpeg`,a=o===`png`?`png`:`jpg`,s=c.metadata?.fileName,l;if(s&&typeof s==`string`)if(i?.naming){let t={...i.naming,autoSuffix:i.naming.autoSuffix??(()=>`thumb`)};l=`${(0,e.getBaseName)((0,e.applyFileNaming)(c,(0,e.buildNamingContext)(c,{flowId:c.flow?.flowId??``,jobId:c.flow?.jobId??``,nodeId:n,nodeType:`thumbnail`},{format:o}),t))}.${a}`}else l=s.replace(/\.[^.]+$/,`.${a}`);return{bytes:t,type:r,fileName:l}})})})}const o={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},s={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`};function c(n,r,i){return t.Effect.gen(function*(){let a=yield*e.VideoPlugin;return yield*(0,e.createTransformNode)({id:n,name:`Transcode`,description:`Converts video to specified format and codec`,nodeTypeId:`transcode-video`,outputTypeId:e.STORAGE_OUTPUT_TYPE_ID,keepOutput:i?.keepOutput,nodeType:`transcode`,namingVars:{format:r.format},transform:(c,l)=>t.Effect.map(a.transcode(c,r),t=>{let a=o[r.format],c=s[r.format],u=l.metadata?.fileName,d;if(u&&typeof u==`string`)if(i?.naming){let t={...i.naming,autoSuffix:i.naming.autoSuffix??(e=>e.format??r.format)};d=`${(0,e.getBaseName)((0,e.applyFileNaming)(l,(0,e.buildNamingContext)(l,{flowId:l.flow?.flowId??``,jobId:l.flow?.jobId??``,nodeId:n,nodeType:`transcode`},{format:r.format}),t))}.${c}`}else d=u.replace(/\.[^.]+$/,`.${c}`);return{bytes:t,type:a,fileName:d}})})})}function l(r,i,a){return t.Effect.gen(function*(){let o=yield*e.VideoPlugin;if(i.endTime!==void 0&&i.endTime<=i.startTime)return yield*n.UploadistaError.fromCode(`VALIDATION_ERROR`,{body:`endTime must be greater than startTime`,details:{params:i}}).toEffect();if(i.duration!==void 0&&i.endTime!==void 0&&i.duration!==i.endTime-i.startTime)return yield*n.UploadistaError.fromCode(`VALIDATION_ERROR`,{body:`Cannot specify both endTime and duration with conflicting values`,details:{params:i}}).toEffect();if(i.duration!==void 0&&i.duration<=0)return yield*n.UploadistaError.fromCode(`VALIDATION_ERROR`,{body:`duration must be greater than 0`,details:{params:i}}).toEffect();let s=a?.naming?{...a.naming,autoSuffix:a.naming.autoSuffix??(()=>`trimmed`)}:void 0;return yield*(0,e.createTransformNode)({id:r,name:`Trim Video`,description:`Extracts a segment from the video`,nodeTypeId:`trim-video`,outputTypeId:e.STORAGE_OUTPUT_TYPE_ID,keepOutput:a?.keepOutput,naming:s,nodeType:`trim`,transform:(e,n)=>t.Effect.map(o.trim(e,i),e=>({bytes:e}))})})}exports.createDescribeVideoNode=r,exports.createTranscodeVideoNode=c,exports.createTrimVideoNode=l,exports.createVideoResizeNode=i,exports.createVideoThumbnailNode=a;
1
+ let e=require(`@uploadista/core/flow`),t=require(`effect`),n=require(`@uploadista/core/errors`);function r(n,r){return t.Effect.gen(function*(){let i=yield*e.VideoPlugin;return yield*(0,e.createTransformNode)({id:n,name:`Describe Video`,description:`Extracts video metadata (duration, resolution, codec, etc.)`,nodeTypeId:`describe-video`,outputTypeId:e.STORAGE_OUTPUT_TYPE_ID,keepOutput:r?.keepOutput,transform:(e,n)=>t.Effect.gen(function*(){let t=yield*i.describe(e);return{bytes:e,metadata:{...n.metadata,videoInfo:t}}})})})}const i={fileSizeThreshold:1e7,chunkSize:1048576};function a(n,r,a){return t.Effect.gen(function*(){let o=yield*e.VideoPlugin,s=o.supportsStreaming??!1,c=a?.mode??`auto`,l=c===`buffered`?`buffered`:s?c:`buffered`,u={...i,...a?.streamingConfig},d=a?.naming?{...a.naming,autoSuffix:a.naming.autoSuffix??(e=>`${e.width??r.width}x${e.height??r.height}`)}:void 0;return yield*(0,e.createTransformNode)({id:n,name:`Resize Video`,description:`Changes video resolution`,nodeTypeId:`resize-video`,outputTypeId:e.STORAGE_OUTPUT_TYPE_ID,keepOutput:a?.keepOutput,naming:d,nodeType:`resize-video`,namingVars:{width:r.width,height:r.height},mode:l,streamingConfig:u,transform:e=>t.Effect.map(o.resize(e,r),e=>({bytes:e})),streamingTransform:o.resizeStream?e=>t.Effect.gen(function*(){let t=o.resizeStream;if(!t)throw Error(`resizeStream not available`);return{stream:yield*t(e,r)}}):void 0})})}function o(n,r,i){return t.Effect.gen(function*(){let a=yield*e.VideoPlugin,o=r.format||`jpeg`;return yield*(0,e.createTransformNode)({id:n,name:`Generate Thumbnail`,description:`Extracts a frame from video as an image`,nodeTypeId:`thumbnail-video`,outputTypeId:e.STORAGE_OUTPUT_TYPE_ID,keepOutput:i?.keepOutput,nodeType:`thumbnail`,namingVars:{format:o},transform:(s,c)=>t.Effect.map(a.extractFrame(s,r),t=>{let r=o===`png`?`image/png`:`image/jpeg`,a=o===`png`?`png`:`jpg`,s=c.metadata?.fileName,l;if(s&&typeof s==`string`)if(i?.naming){let t={...i.naming,autoSuffix:i.naming.autoSuffix??(()=>`thumb`)};l=`${(0,e.getBaseName)((0,e.applyFileNaming)(c,(0,e.buildNamingContext)(c,{flowId:c.flow?.flowId??``,jobId:c.flow?.jobId??``,nodeId:n,nodeType:`thumbnail`},{format:o}),t))}.${a}`}else l=s.replace(/\.[^.]+$/,`.${a}`);return{bytes:t,type:r,fileName:l}})})})}const s={fileSizeThreshold:1e7,chunkSize:1048576},c={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},l={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`};function u(n,r,i){return t.Effect.gen(function*(){let a=yield*e.VideoPlugin,o=a.supportsStreaming??!1,u=i?.mode??`auto`,d=u===`buffered`?`buffered`:o?u:`buffered`,f={...s,...i?.streamingConfig},p=t=>{let a=c[r.format],o=l[r.format],s=t.metadata?.fileName,u;if(s&&typeof s==`string`)if(i?.naming){let a={...i.naming,autoSuffix:i.naming.autoSuffix??(e=>e.format??r.format)};u=`${(0,e.getBaseName)((0,e.applyFileNaming)(t,(0,e.buildNamingContext)(t,{flowId:t.flow?.flowId??``,jobId:t.flow?.jobId??``,nodeId:n,nodeType:`transcode`},{format:r.format}),a))}.${o}`}else u=s.replace(/\.[^.]+$/,`.${o}`);return{newType:a,newFileName:u}};return yield*(0,e.createTransformNode)({id:n,name:`Transcode`,description:`Converts video to specified format and codec`,nodeTypeId:`transcode-video`,outputTypeId:e.STORAGE_OUTPUT_TYPE_ID,keepOutput:i?.keepOutput,nodeType:`transcode`,namingVars:{format:r.format},mode:d,streamingConfig:f,transform:(e,n)=>t.Effect.map(a.transcode(e,r),e=>{let{newType:t,newFileName:r}=p(n);return{bytes:e,type:t,fileName:r}}),streamingTransform:a.transcodeStream?(e,n)=>t.Effect.gen(function*(){let t=a.transcodeStream;if(!t)throw Error(`transcodeStream not available`);let i=yield*t(e,r),{newType:o,newFileName:s}=p(n);return{stream:i,type:o,fileName:s}}):void 0})})}const d={fileSizeThreshold:1e7,chunkSize:1048576};function f(r,i,a){return t.Effect.gen(function*(){let o=yield*e.VideoPlugin;if(i.endTime!==void 0&&i.endTime<=i.startTime)return yield*n.UploadistaError.fromCode(`VALIDATION_ERROR`,{body:`endTime must be greater than startTime`,details:{params:i}}).toEffect();if(i.duration!==void 0&&i.endTime!==void 0&&i.duration!==i.endTime-i.startTime)return yield*n.UploadistaError.fromCode(`VALIDATION_ERROR`,{body:`Cannot specify both endTime and duration with conflicting values`,details:{params:i}}).toEffect();if(i.duration!==void 0&&i.duration<=0)return yield*n.UploadistaError.fromCode(`VALIDATION_ERROR`,{body:`duration must be greater than 0`,details:{params:i}}).toEffect();let s=o.supportsStreaming??!1,c=a?.mode??`auto`,l=c===`buffered`?`buffered`:s?c:`buffered`,u={...d,...a?.streamingConfig},f=a?.naming?{...a.naming,autoSuffix:a.naming.autoSuffix??(()=>`trimmed`)}:void 0;return yield*(0,e.createTransformNode)({id:r,name:`Trim Video`,description:`Extracts a segment from the video`,nodeTypeId:`trim-video`,outputTypeId:e.STORAGE_OUTPUT_TYPE_ID,keepOutput:a?.keepOutput,naming:f,nodeType:`trim`,mode:l,streamingConfig:u,transform:e=>t.Effect.map(o.trim(e,i),e=>({bytes:e})),streamingTransform:o.trimStream?e=>t.Effect.gen(function*(){let t=o.trimStream;if(!t)throw Error(`trimStream not available`);return{stream:yield*t(e,i)}}):void 0})})}exports.createDescribeVideoNode=r,exports.createTranscodeVideoNode=u,exports.createTrimVideoNode=f,exports.createVideoResizeNode=a,exports.createVideoThumbnailNode=o;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _uploadista_core_flow3 from "@uploadista/core/flow";
2
- import { DescribeVideoMetadata, ExtractFrameVideoParams, ExtractFrameVideoParams as ExtractFrameVideoParams$1, FileNamingConfig, ResizeVideoParams, ResizeVideoParams as ResizeVideoParams$1, TranscodeVideoParams, TranscodeVideoParams as TranscodeVideoParams$1, TrimVideoParams, TrimVideoParams as TrimVideoParams$1, VideoPlugin } from "@uploadista/core/flow";
2
+ import { DescribeVideoMetadata, ExtractFrameVideoParams, ExtractFrameVideoParams as ExtractFrameVideoParams$1, FileNamingConfig, ResizeVideoParams, ResizeVideoParams as ResizeVideoParams$1, StreamingConfig, TranscodeVideoParams, TranscodeVideoParams as TranscodeVideoParams$1, TransformMode, TrimVideoParams, TrimVideoParams as TrimVideoParams$1, VideoPlugin } from "@uploadista/core/flow";
3
3
  import * as _uploadista_core_types5 from "@uploadista/core/types";
4
4
  import * as zod_v4_core1 from "zod/v4/core";
5
5
  import * as zod1 from "zod";
@@ -62,15 +62,21 @@ declare function createDescribeVideoNode(id: string, options?: {
62
62
  *
63
63
  * Changes video resolution while optionally maintaining aspect ratio.
64
64
  *
65
+ * Supports both buffered and streaming modes for memory-efficient processing
66
+ * of large videos. In streaming mode, the output is streamed directly to storage,
67
+ * reducing peak memory usage.
68
+ *
65
69
  * @param id - Unique node identifier
66
70
  * @param params - Resize parameters
67
71
  * @param options - Optional configuration
68
72
  * @param options.keepOutput - Whether to keep output in flow results
69
73
  * @param options.naming - File naming configuration (auto suffix: `${width}x${height}`)
74
+ * @param options.mode - Transform mode: "buffered", "streaming", or "auto" (default)
75
+ * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)
70
76
  *
71
77
  * @example
72
78
  * ```typescript
73
- * // With auto-naming: "video.mp4" -> "video-1280x720.mp4"
79
+ * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered
74
80
  * const node = yield* createVideoResizeNode("resize-1", {
75
81
  * width: 1280,
76
82
  * height: 720,
@@ -79,11 +85,31 @@ declare function createDescribeVideoNode(id: string, options?: {
79
85
  * }, {
80
86
  * naming: { mode: "auto" }
81
87
  * });
88
+ *
89
+ * // Force buffered mode for small files
90
+ * const nodeBuffered = yield* createVideoResizeNode("resize-2", {
91
+ * width: 1920,
92
+ * height: 1080
93
+ * }, {
94
+ * mode: "buffered",
95
+ * naming: { mode: "auto" }
96
+ * });
97
+ *
98
+ * // Force streaming mode for memory efficiency
99
+ * const nodeStreaming = yield* createVideoResizeNode("resize-3", {
100
+ * width: 1280,
101
+ * height: 720
102
+ * }, {
103
+ * mode: "streaming",
104
+ * naming: { mode: "auto" }
105
+ * });
82
106
  * ```
83
107
  */
84
108
  declare function createVideoResizeNode(id: string, params: ResizeVideoParams$1, options?: {
85
109
  keepOutput?: boolean;
86
110
  naming?: FileNamingConfig;
111
+ mode?: TransformMode;
112
+ streamingConfig?: StreamingConfig;
87
113
  }): Effect.Effect<_uploadista_core_flow3.FlowNodeData & {
88
114
  inputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
89
115
  outputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
@@ -175,15 +201,21 @@ declare function createVideoThumbnailNode(id: string, params: ExtractFrameVideoP
175
201
  *
176
202
  * Converts video to specified format and codec, optionally adjusting bitrates.
177
203
  *
204
+ * Supports both buffered and streaming modes for memory-efficient processing
205
+ * of large videos. In streaming mode, the output is streamed directly to storage,
206
+ * reducing peak memory usage.
207
+ *
178
208
  * @param id - Unique node identifier
179
209
  * @param params - Transcode parameters
180
210
  * @param options - Optional configuration
181
211
  * @param options.keepOutput - Whether to keep output in flow results
182
212
  * @param options.naming - File naming configuration (auto suffix: `${format}`)
213
+ * @param options.mode - Transform mode: "buffered", "streaming", or "auto" (default)
214
+ * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)
183
215
  *
184
216
  * @example
185
217
  * ```typescript
186
- * // With auto-naming: "video.mov" -> "video-webm.webm"
218
+ * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered
187
219
  * const node = yield* createTranscodeVideoNode("transcode-1", {
188
220
  * format: "webm",
189
221
  * codec: "vp9",
@@ -191,11 +223,31 @@ declare function createVideoThumbnailNode(id: string, params: ExtractFrameVideoP
191
223
  * }, {
192
224
  * naming: { mode: "auto" }
193
225
  * });
226
+ *
227
+ * // Force buffered mode for small files
228
+ * const nodeBuffered = yield* createTranscodeVideoNode("transcode-2", {
229
+ * format: "mp4",
230
+ * codec: "h264"
231
+ * }, {
232
+ * mode: "buffered",
233
+ * naming: { mode: "auto" }
234
+ * });
235
+ *
236
+ * // Force streaming mode for memory efficiency
237
+ * const nodeStreaming = yield* createTranscodeVideoNode("transcode-3", {
238
+ * format: "mp4",
239
+ * codec: "h264"
240
+ * }, {
241
+ * mode: "streaming",
242
+ * naming: { mode: "auto" }
243
+ * });
194
244
  * ```
195
245
  */
196
246
  declare function createTranscodeVideoNode(id: string, params: TranscodeVideoParams$1, options?: {
197
247
  keepOutput?: boolean;
198
248
  naming?: FileNamingConfig;
249
+ mode?: TransformMode;
250
+ streamingConfig?: StreamingConfig;
199
251
  }): Effect.Effect<_uploadista_core_flow3.FlowNodeData & {
200
252
  inputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
201
253
  outputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
@@ -231,26 +283,52 @@ declare function createTranscodeVideoNode(id: string, params: TranscodeVideoPara
231
283
  *
232
284
  * Extracts a segment from the video by time range.
233
285
  *
286
+ * Supports both buffered and streaming modes for memory-efficient processing
287
+ * of large videos. In streaming mode, the output is streamed directly to storage,
288
+ * reducing peak memory usage.
289
+ *
234
290
  * @param id - Unique node identifier
235
291
  * @param params - Trim parameters
236
292
  * @param options - Optional configuration
237
293
  * @param options.keepOutput - Whether to keep output in flow results
238
294
  * @param options.naming - File naming configuration (auto suffix: `trimmed`)
295
+ * @param options.mode - Transform mode: "buffered", "streaming", or "auto" (default)
296
+ * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)
239
297
  *
240
298
  * @example
241
299
  * ```typescript
242
- * // With auto-naming: "video.mp4" -> "video-trimmed.mp4"
300
+ * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered
243
301
  * const node = yield* createTrimVideoNode("trim-1", {
244
302
  * startTime: 10,
245
303
  * endTime: 30
246
304
  * }, {
247
305
  * naming: { mode: "auto" }
248
306
  * });
307
+ *
308
+ * // Force buffered mode for small files
309
+ * const nodeBuffered = yield* createTrimVideoNode("trim-2", {
310
+ * startTime: 0,
311
+ * duration: 60
312
+ * }, {
313
+ * mode: "buffered",
314
+ * naming: { mode: "auto" }
315
+ * });
316
+ *
317
+ * // Force streaming mode for memory efficiency
318
+ * const nodeStreaming = yield* createTrimVideoNode("trim-3", {
319
+ * startTime: 5,
320
+ * endTime: 25
321
+ * }, {
322
+ * mode: "streaming",
323
+ * naming: { mode: "auto" }
324
+ * });
249
325
  * ```
250
326
  */
251
327
  declare function createTrimVideoNode(id: string, params: TrimVideoParams$1, options?: {
252
328
  keepOutput?: boolean;
253
329
  naming?: FileNamingConfig;
330
+ mode?: TransformMode;
331
+ streamingConfig?: StreamingConfig;
254
332
  }): Effect.Effect<_uploadista_core_flow3.FlowNodeData & {
255
333
  inputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
256
334
  outputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/describe-video-node.ts","../src/resize-node.ts","../src/thumbnail-node.ts","../src/transcode-node.ts","../src/trim-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA;;;;;;iBAAgB,uBAAA;;IAEoB,MAAA,CAAA,OAFG,sBAAA,CAEH,YAAA;4BAAA,uBAAA,CAAA,UAAA;;EA8BimsL,GAAA,EAAA,CAAA,IAAA,EAAA;IAAA,IAAA,oCAAA;;;;aAAA;;iEAAA,uBAAA,CAAA,UAAA;;;IA9BjmsL,QAAA,EAAA,MAAA;IAAA,KAAA,EAAA,OAAA;;;;ECSpB,QAAA,CAAA,EAAA,OAAA;EAEN,KAAA,CAAA,EAAA;IACmC,UAAA,CAAA,EAAA,MAAA;IAAgB,UAAA,CAAA,EAAA,MAAE;IAAA,kBAAA,CAAA,EAAA,OAAA;;;;;;;;;;;;;;;;;ADd/D;;;;;;;;;;;;;;;iBCWgB,qBAAA,qBAEN;;WACmC;IAAkB,MAAA,CAAA,OAAF,sBAAA,CAAE,YAAA;4BAAA,uBAAA,CAAA,UAAA;;EDZ3B,GAAA,EAAA,CAAA,IAAA,EAAA;IAAA,IAAA,oCAAA;;;;ICSpB,MAAA,CAAA,EAsCkmrL,MAtClmrL,CAAA,MAAqB,EAAA,OAAA,CAAA;IAE3B,QAAA,EAAA,MAAA,GAAA,IAAA;EACmC,CAAA,EAAA,gBAAA,2CAAA,CAmCqkrL,uBAAA,CAAA,UAAA,CAnCrkrL,4CAAA,KAAA,CAAA;EAAgB,SAAA,CAAA,EAAA;IAAE,KAAA,EAAA,MAAA;;;;;;;;;IAmCmjrL,UAAA,CAAA,EAAA,MAAA;IAAA,kBAAA,CAAA,EAAA,OAAA;;;;;;;;;;;;;;;;;ADjDlnrL;;;;;;;;;;;;;;iBEagB,wBAAA,qBAEN;;WACmC;IAAkB,MAAA,CAAA,OAAF,sBAAA,CAAE,YAAA;4BAAA,uBAAA,CAAA,UAAA;;;IFd3B,IAAA,oCAAA;IAAA,KAAA,EAAA,MAAA;;;aEgF8zoL;IDvEl1oL,QAAA,EAAA,MAAA,GAAA,IAAqB;EAE3B,CAAA,EAAA,gBAAA,2CAAA,CCqEw1oL,uBAAA,CAAA,UAAA,CDrEx1oL,4CAAA,KAAA,CAAA;EACmC,SAAA,CAAA,EAAA;IAAgB,KAAA,EAAA,MAAA;IAAE,QAAA,EAAA,MAAA;;;;;;;;;IAmCmjrL,kBAAA,CAAA,EAAA,OAAA;EAAA,CAAA;;;;;;;;;;;;;;;;ADjDlnrL;;;;;;;;;;;;;;iBG6BgB,wBAAA,qBAEN;;WACmC;IAAkB,MAAA,CAAA,OAAF,sBAAA,CAAE,YAAA;4BAAA,uBAAA,CAAA,UAAA;;;IH9B3B,IAAA,oCAAA;IAAA,KAAA,EAAA,MAAA;;;aG+F45nL;IFtFh7nL,QAAA,EAAA,MAAA,GAAA,IAAqB;EAE3B,CAAA,EAAA,gBAAA,2CAAA,CEoFs7nL,uBAAA,CAAA,UAAA,CFpFt7nL,4CAAA,KAAA,CAAA;EACmC,SAAA,CAAA,EAAA;IAAgB,KAAA,EAAA,MAAA;IAAE,QAAA,EAAA,MAAA;;;;;;;;;IAmCmjrL,kBAAA,CAAA,EAAA,OAAA;EAAA,CAAA;;;;;;;;;;;;;;;ADjDlnrL;;;;;;;;;;;;;;iBIUgB,mBAAA,qBAEN;;WACmC;IAAkB,MAAA,CAAA,OAAF,sBAAA,CAAE,YAAA;4BAAA,uBAAA,CAAA,UAAA;;;IJX3B,IAAA,oCAAA;IAAA,KAAA,EAAA,MAAA;;;aIqE67pL;IH5Dj9pL,QAAA,EAAA,MAAA,GAAA,IAAqB;EAE3B,CAAA,EAAA,gBAAA,2CAAA,CG0Du9pL,uBAAA,CAAA,UAAA,CH1Dv9pL,iBAAA,EAAA,KAAA,CAAA;EACmC,SAAA,CAAA,EAAA;IAAgB,KAAA,EAAA,MAAA;IAAE,QAAA,EAAA,MAAA;;;;;;;;;IAmCmjrL,kBAAA,CAAA,EAAA,OAAA;EAAA,CAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/describe-video-node.ts","../src/resize-node.ts","../src/thumbnail-node.ts","../src/transcode-node.ts","../src/trim-node.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _uploadista_core_flow3 from "@uploadista/core/flow";
2
- import { DescribeVideoMetadata, ExtractFrameVideoParams, ExtractFrameVideoParams as ExtractFrameVideoParams$1, FileNamingConfig, ResizeVideoParams, ResizeVideoParams as ResizeVideoParams$1, TranscodeVideoParams, TranscodeVideoParams as TranscodeVideoParams$1, TrimVideoParams, TrimVideoParams as TrimVideoParams$1, VideoPlugin } from "@uploadista/core/flow";
2
+ import { DescribeVideoMetadata, ExtractFrameVideoParams, ExtractFrameVideoParams as ExtractFrameVideoParams$1, FileNamingConfig, ResizeVideoParams, ResizeVideoParams as ResizeVideoParams$1, StreamingConfig, TranscodeVideoParams, TranscodeVideoParams as TranscodeVideoParams$1, TransformMode, TrimVideoParams, TrimVideoParams as TrimVideoParams$1, VideoPlugin } from "@uploadista/core/flow";
3
3
  import { Effect } from "effect";
4
4
  import * as _uploadista_core_errors0 from "@uploadista/core/errors";
5
5
  import { UploadistaError } from "@uploadista/core/errors";
@@ -62,15 +62,21 @@ declare function createDescribeVideoNode(id: string, options?: {
62
62
  *
63
63
  * Changes video resolution while optionally maintaining aspect ratio.
64
64
  *
65
+ * Supports both buffered and streaming modes for memory-efficient processing
66
+ * of large videos. In streaming mode, the output is streamed directly to storage,
67
+ * reducing peak memory usage.
68
+ *
65
69
  * @param id - Unique node identifier
66
70
  * @param params - Resize parameters
67
71
  * @param options - Optional configuration
68
72
  * @param options.keepOutput - Whether to keep output in flow results
69
73
  * @param options.naming - File naming configuration (auto suffix: `${width}x${height}`)
74
+ * @param options.mode - Transform mode: "buffered", "streaming", or "auto" (default)
75
+ * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)
70
76
  *
71
77
  * @example
72
78
  * ```typescript
73
- * // With auto-naming: "video.mp4" -> "video-1280x720.mp4"
79
+ * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered
74
80
  * const node = yield* createVideoResizeNode("resize-1", {
75
81
  * width: 1280,
76
82
  * height: 720,
@@ -79,11 +85,31 @@ declare function createDescribeVideoNode(id: string, options?: {
79
85
  * }, {
80
86
  * naming: { mode: "auto" }
81
87
  * });
88
+ *
89
+ * // Force buffered mode for small files
90
+ * const nodeBuffered = yield* createVideoResizeNode("resize-2", {
91
+ * width: 1920,
92
+ * height: 1080
93
+ * }, {
94
+ * mode: "buffered",
95
+ * naming: { mode: "auto" }
96
+ * });
97
+ *
98
+ * // Force streaming mode for memory efficiency
99
+ * const nodeStreaming = yield* createVideoResizeNode("resize-3", {
100
+ * width: 1280,
101
+ * height: 720
102
+ * }, {
103
+ * mode: "streaming",
104
+ * naming: { mode: "auto" }
105
+ * });
82
106
  * ```
83
107
  */
84
108
  declare function createVideoResizeNode(id: string, params: ResizeVideoParams$1, options?: {
85
109
  keepOutput?: boolean;
86
110
  naming?: FileNamingConfig;
111
+ mode?: TransformMode;
112
+ streamingConfig?: StreamingConfig;
87
113
  }): Effect.Effect<_uploadista_core_flow3.FlowNodeData & {
88
114
  inputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
89
115
  outputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
@@ -175,15 +201,21 @@ declare function createVideoThumbnailNode(id: string, params: ExtractFrameVideoP
175
201
  *
176
202
  * Converts video to specified format and codec, optionally adjusting bitrates.
177
203
  *
204
+ * Supports both buffered and streaming modes for memory-efficient processing
205
+ * of large videos. In streaming mode, the output is streamed directly to storage,
206
+ * reducing peak memory usage.
207
+ *
178
208
  * @param id - Unique node identifier
179
209
  * @param params - Transcode parameters
180
210
  * @param options - Optional configuration
181
211
  * @param options.keepOutput - Whether to keep output in flow results
182
212
  * @param options.naming - File naming configuration (auto suffix: `${format}`)
213
+ * @param options.mode - Transform mode: "buffered", "streaming", or "auto" (default)
214
+ * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)
183
215
  *
184
216
  * @example
185
217
  * ```typescript
186
- * // With auto-naming: "video.mov" -> "video-webm.webm"
218
+ * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered
187
219
  * const node = yield* createTranscodeVideoNode("transcode-1", {
188
220
  * format: "webm",
189
221
  * codec: "vp9",
@@ -191,11 +223,31 @@ declare function createVideoThumbnailNode(id: string, params: ExtractFrameVideoP
191
223
  * }, {
192
224
  * naming: { mode: "auto" }
193
225
  * });
226
+ *
227
+ * // Force buffered mode for small files
228
+ * const nodeBuffered = yield* createTranscodeVideoNode("transcode-2", {
229
+ * format: "mp4",
230
+ * codec: "h264"
231
+ * }, {
232
+ * mode: "buffered",
233
+ * naming: { mode: "auto" }
234
+ * });
235
+ *
236
+ * // Force streaming mode for memory efficiency
237
+ * const nodeStreaming = yield* createTranscodeVideoNode("transcode-3", {
238
+ * format: "mp4",
239
+ * codec: "h264"
240
+ * }, {
241
+ * mode: "streaming",
242
+ * naming: { mode: "auto" }
243
+ * });
194
244
  * ```
195
245
  */
196
246
  declare function createTranscodeVideoNode(id: string, params: TranscodeVideoParams$1, options?: {
197
247
  keepOutput?: boolean;
198
248
  naming?: FileNamingConfig;
249
+ mode?: TransformMode;
250
+ streamingConfig?: StreamingConfig;
199
251
  }): Effect.Effect<_uploadista_core_flow3.FlowNodeData & {
200
252
  inputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
201
253
  outputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
@@ -231,26 +283,52 @@ declare function createTranscodeVideoNode(id: string, params: TranscodeVideoPara
231
283
  *
232
284
  * Extracts a segment from the video by time range.
233
285
  *
286
+ * Supports both buffered and streaming modes for memory-efficient processing
287
+ * of large videos. In streaming mode, the output is streamed directly to storage,
288
+ * reducing peak memory usage.
289
+ *
234
290
  * @param id - Unique node identifier
235
291
  * @param params - Trim parameters
236
292
  * @param options - Optional configuration
237
293
  * @param options.keepOutput - Whether to keep output in flow results
238
294
  * @param options.naming - File naming configuration (auto suffix: `trimmed`)
295
+ * @param options.mode - Transform mode: "buffered", "streaming", or "auto" (default)
296
+ * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)
239
297
  *
240
298
  * @example
241
299
  * ```typescript
242
- * // With auto-naming: "video.mp4" -> "video-trimmed.mp4"
300
+ * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered
243
301
  * const node = yield* createTrimVideoNode("trim-1", {
244
302
  * startTime: 10,
245
303
  * endTime: 30
246
304
  * }, {
247
305
  * naming: { mode: "auto" }
248
306
  * });
307
+ *
308
+ * // Force buffered mode for small files
309
+ * const nodeBuffered = yield* createTrimVideoNode("trim-2", {
310
+ * startTime: 0,
311
+ * duration: 60
312
+ * }, {
313
+ * mode: "buffered",
314
+ * naming: { mode: "auto" }
315
+ * });
316
+ *
317
+ * // Force streaming mode for memory efficiency
318
+ * const nodeStreaming = yield* createTrimVideoNode("trim-3", {
319
+ * startTime: 5,
320
+ * endTime: 25
321
+ * }, {
322
+ * mode: "streaming",
323
+ * naming: { mode: "auto" }
324
+ * });
249
325
  * ```
250
326
  */
251
327
  declare function createTrimVideoNode(id: string, params: TrimVideoParams$1, options?: {
252
328
  keepOutput?: boolean;
253
329
  naming?: FileNamingConfig;
330
+ mode?: TransformMode;
331
+ streamingConfig?: StreamingConfig;
254
332
  }): Effect.Effect<_uploadista_core_flow3.FlowNodeData & {
255
333
  inputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
256
334
  outputSchema: zod1.ZodType<_uploadista_core_types5.UploadFile, unknown, zod_v4_core1.$ZodTypeInternals<_uploadista_core_types5.UploadFile, unknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/describe-video-node.ts","../src/resize-node.ts","../src/thumbnail-node.ts","../src/transcode-node.ts","../src/trim-node.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AAsBA;;;;;;iBAAgB,uBAAA;;IAEoB,MAAA,CAAA,OAFG,sBAAA,CAEH,YAAA;4BAAA,uBAAA,CAAA,UAAA;;EA8BimsL,GAAA,EAAA,CAAA,IAAA,EAAA;IAAA,IAAA,oCAAA;;;;aAAA;;iEAAA,uBAAA,CAAA,UAAA;;;IA9BjmsL,QAAA,EAAA,MAAA;IAAA,KAAA,EAAA,OAAA;;;;ECSpB,QAAA,CAAA,EAAA,OAAA;EAEN,KAAA,CAAA,EAAA;IACmC,UAAA,CAAA,EAAA,MAAA;IAAgB,UAAA,CAAA,EAAA,MAAE;IAAA,kBAAA,CAAA,EAAA,OAAA;;;;;;;;;;;;;;;;;ADd/D;;;;;;;;;;;;;;;iBCWgB,qBAAA,qBAEN;;WACmC;IAAkB,MAAA,CAAA,OAAF,sBAAA,CAAE,YAAA;4BAAA,uBAAA,CAAA,UAAA;;EDZ3B,GAAA,EAAA,CAAA,IAAA,EAAA;IAAA,IAAA,oCAAA;;;;ICSpB,MAAA,CAAA,EAsCkmrL,MAtClmrL,CAAA,MAAqB,EAAA,OAAA,CAAA;IAE3B,QAAA,EAAA,MAAA,GAAA,IAAA;EACmC,CAAA,EAAA,gBAAA,2CAAA,CAmCqkrL,uBAAA,CAAA,UAAA,CAnCrkrL,4CAAA,KAAA,CAAA;EAAgB,SAAA,CAAA,EAAA;IAAE,KAAA,EAAA,MAAA;;;;;;;;;IAmCmjrL,UAAA,CAAA,EAAA,MAAA;IAAA,kBAAA,CAAA,EAAA,OAAA;;;;;;;;;;;;;;;;;ADjDlnrL;;;;;;;;;;;;;;iBEagB,wBAAA,qBAEN;;WACmC;IAAkB,MAAA,CAAA,OAAF,sBAAA,CAAE,YAAA;4BAAA,uBAAA,CAAA,UAAA;;;IFd3B,IAAA,oCAAA;IAAA,KAAA,EAAA,MAAA;;;aEgF8zoL;IDvEl1oL,QAAA,EAAA,MAAA,GAAA,IAAqB;EAE3B,CAAA,EAAA,gBAAA,2CAAA,CCqEw1oL,uBAAA,CAAA,UAAA,CDrEx1oL,4CAAA,KAAA,CAAA;EACmC,SAAA,CAAA,EAAA;IAAgB,KAAA,EAAA,MAAA;IAAE,QAAA,EAAA,MAAA;;;;;;;;;IAmCmjrL,kBAAA,CAAA,EAAA,OAAA;EAAA,CAAA;;;;;;;;;;;;;;;;ADjDlnrL;;;;;;;;;;;;;;iBG6BgB,wBAAA,qBAEN;;WACmC;IAAkB,MAAA,CAAA,OAAF,sBAAA,CAAE,YAAA;4BAAA,uBAAA,CAAA,UAAA;;;IH9B3B,IAAA,oCAAA;IAAA,KAAA,EAAA,MAAA;;;aG+F45nL;IFtFh7nL,QAAA,EAAA,MAAA,GAAA,IAAqB;EAE3B,CAAA,EAAA,gBAAA,2CAAA,CEoFs7nL,uBAAA,CAAA,UAAA,CFpFt7nL,4CAAA,KAAA,CAAA;EACmC,SAAA,CAAA,EAAA;IAAgB,KAAA,EAAA,MAAA;IAAE,QAAA,EAAA,MAAA;;;;;;;;;IAmCmjrL,kBAAA,CAAA,EAAA,OAAA;EAAA,CAAA;;;;;;;;;;;;;;;ADjDlnrL;;;;;;;;;;;;;;iBIUgB,mBAAA,qBAEN;;WACmC;IAAkB,MAAA,CAAA,OAAF,sBAAA,CAAE,YAAA;4BAAA,uBAAA,CAAA,UAAA;;;IJX3B,IAAA,oCAAA;IAAA,KAAA,EAAA,MAAA;;;aIqE67pL;IH5Dj9pL,QAAA,EAAA,MAAA,GAAA,IAAqB;EAE3B,CAAA,EAAA,gBAAA,2CAAA,CG0Du9pL,uBAAA,CAAA,UAAA,CH1Dv9pL,iBAAA,EAAA,KAAA,CAAA;EACmC,SAAA,CAAA,EAAA;IAAgB,KAAA,EAAA,MAAA;IAAE,QAAA,EAAA,MAAA;;;;;;;;;IAmCmjrL,kBAAA,CAAA,EAAA,OAAA;EAAA,CAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/describe-video-node.ts","../src/resize-node.ts","../src/thumbnail-node.ts","../src/transcode-node.ts","../src/trim-node.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{STORAGE_OUTPUT_TYPE_ID as e,VideoPlugin as t,applyFileNaming as n,buildNamingContext as r,createTransformNode as i,getBaseName as a}from"@uploadista/core/flow";import{Effect as o}from"effect";import{UploadistaError as s}from"@uploadista/core/errors";function c(n,r){return o.gen(function*(){let a=yield*t;return yield*i({id:n,name:`Describe Video`,description:`Extracts video metadata (duration, resolution, codec, etc.)`,nodeTypeId:`describe-video`,outputTypeId:e,keepOutput:r?.keepOutput,transform:(e,t)=>o.gen(function*(){let n=yield*a.describe(e);return{bytes:e,metadata:{...t.metadata,videoInfo:n}}})})})}function l(n,r,a){return o.gen(function*(){let s=yield*t,c=a?.naming?{...a.naming,autoSuffix:a.naming.autoSuffix??(e=>`${e.width??r.width}x${e.height??r.height}`)}:void 0;return yield*i({id:n,name:`Resize Video`,description:`Changes video resolution`,nodeTypeId:`resize-video`,outputTypeId:e,keepOutput:a?.keepOutput,naming:c,nodeType:`resize-video`,namingVars:{width:r.width,height:r.height},transform:(e,t)=>o.map(s.resize(e,r),e=>({bytes:e}))})})}function u(s,c,l){return o.gen(function*(){let u=yield*t,d=c.format||`jpeg`;return yield*i({id:s,name:`Generate Thumbnail`,description:`Extracts a frame from video as an image`,nodeTypeId:`thumbnail-video`,outputTypeId:e,keepOutput:l?.keepOutput,nodeType:`thumbnail`,namingVars:{format:d},transform:(e,t)=>o.map(u.extractFrame(e,c),e=>{let i=d===`png`?`image/png`:`image/jpeg`,o=d===`png`?`png`:`jpg`,c=t.metadata?.fileName,u;if(c&&typeof c==`string`)if(l?.naming){let e={...l.naming,autoSuffix:l.naming.autoSuffix??(()=>`thumb`)};u=`${a(n(t,r(t,{flowId:t.flow?.flowId??``,jobId:t.flow?.jobId??``,nodeId:s,nodeType:`thumbnail`},{format:d}),e))}.${o}`}else u=c.replace(/\.[^.]+$/,`.${o}`);return{bytes:e,type:i,fileName:u}})})})}const d={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},f={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`};function p(s,c,l){return o.gen(function*(){let u=yield*t;return yield*i({id:s,name:`Transcode`,description:`Converts video to specified format and codec`,nodeTypeId:`transcode-video`,outputTypeId:e,keepOutput:l?.keepOutput,nodeType:`transcode`,namingVars:{format:c.format},transform:(e,t)=>o.map(u.transcode(e,c),e=>{let i=d[c.format],o=f[c.format],u=t.metadata?.fileName,p;if(u&&typeof u==`string`)if(l?.naming){let e={...l.naming,autoSuffix:l.naming.autoSuffix??(e=>e.format??c.format)};p=`${a(n(t,r(t,{flowId:t.flow?.flowId??``,jobId:t.flow?.jobId??``,nodeId:s,nodeType:`transcode`},{format:c.format}),e))}.${o}`}else p=u.replace(/\.[^.]+$/,`.${o}`);return{bytes:e,type:i,fileName:p}})})})}function m(n,r,a){return o.gen(function*(){let c=yield*t;if(r.endTime!==void 0&&r.endTime<=r.startTime)return yield*s.fromCode(`VALIDATION_ERROR`,{body:`endTime must be greater than startTime`,details:{params:r}}).toEffect();if(r.duration!==void 0&&r.endTime!==void 0&&r.duration!==r.endTime-r.startTime)return yield*s.fromCode(`VALIDATION_ERROR`,{body:`Cannot specify both endTime and duration with conflicting values`,details:{params:r}}).toEffect();if(r.duration!==void 0&&r.duration<=0)return yield*s.fromCode(`VALIDATION_ERROR`,{body:`duration must be greater than 0`,details:{params:r}}).toEffect();let l=a?.naming?{...a.naming,autoSuffix:a.naming.autoSuffix??(()=>`trimmed`)}:void 0;return yield*i({id:n,name:`Trim Video`,description:`Extracts a segment from the video`,nodeTypeId:`trim-video`,outputTypeId:e,keepOutput:a?.keepOutput,naming:l,nodeType:`trim`,transform:(e,t)=>o.map(c.trim(e,r),e=>({bytes:e}))})})}export{c as createDescribeVideoNode,p as createTranscodeVideoNode,m as createTrimVideoNode,l as createVideoResizeNode,u as createVideoThumbnailNode};
1
+ import{STORAGE_OUTPUT_TYPE_ID as e,VideoPlugin as t,applyFileNaming as n,buildNamingContext as r,createTransformNode as i,getBaseName as a}from"@uploadista/core/flow";import{Effect as o}from"effect";import{UploadistaError as s}from"@uploadista/core/errors";function c(n,r){return o.gen(function*(){let a=yield*t;return yield*i({id:n,name:`Describe Video`,description:`Extracts video metadata (duration, resolution, codec, etc.)`,nodeTypeId:`describe-video`,outputTypeId:e,keepOutput:r?.keepOutput,transform:(e,t)=>o.gen(function*(){let n=yield*a.describe(e);return{bytes:e,metadata:{...t.metadata,videoInfo:n}}})})})}const l={fileSizeThreshold:1e7,chunkSize:1048576};function u(n,r,a){return o.gen(function*(){let s=yield*t,c=s.supportsStreaming??!1,u=a?.mode??`auto`,d=u===`buffered`?`buffered`:c?u:`buffered`,f={...l,...a?.streamingConfig},p=a?.naming?{...a.naming,autoSuffix:a.naming.autoSuffix??(e=>`${e.width??r.width}x${e.height??r.height}`)}:void 0;return yield*i({id:n,name:`Resize Video`,description:`Changes video resolution`,nodeTypeId:`resize-video`,outputTypeId:e,keepOutput:a?.keepOutput,naming:p,nodeType:`resize-video`,namingVars:{width:r.width,height:r.height},mode:d,streamingConfig:f,transform:e=>o.map(s.resize(e,r),e=>({bytes:e})),streamingTransform:s.resizeStream?e=>o.gen(function*(){let t=s.resizeStream;if(!t)throw Error(`resizeStream not available`);return{stream:yield*t(e,r)}}):void 0})})}function d(s,c,l){return o.gen(function*(){let u=yield*t,d=c.format||`jpeg`;return yield*i({id:s,name:`Generate Thumbnail`,description:`Extracts a frame from video as an image`,nodeTypeId:`thumbnail-video`,outputTypeId:e,keepOutput:l?.keepOutput,nodeType:`thumbnail`,namingVars:{format:d},transform:(e,t)=>o.map(u.extractFrame(e,c),e=>{let i=d===`png`?`image/png`:`image/jpeg`,o=d===`png`?`png`:`jpg`,c=t.metadata?.fileName,u;if(c&&typeof c==`string`)if(l?.naming){let e={...l.naming,autoSuffix:l.naming.autoSuffix??(()=>`thumb`)};u=`${a(n(t,r(t,{flowId:t.flow?.flowId??``,jobId:t.flow?.jobId??``,nodeId:s,nodeType:`thumbnail`},{format:d}),e))}.${o}`}else u=c.replace(/\.[^.]+$/,`.${o}`);return{bytes:e,type:i,fileName:u}})})})}const f={fileSizeThreshold:1e7,chunkSize:1048576},p={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},m={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`};function h(s,c,l){return o.gen(function*(){let u=yield*t,d=u.supportsStreaming??!1,h=l?.mode??`auto`,g=h===`buffered`?`buffered`:d?h:`buffered`,_={...f,...l?.streamingConfig},v=e=>{let t=p[c.format],i=m[c.format],o=e.metadata?.fileName,u;if(o&&typeof o==`string`)if(l?.naming){let t={...l.naming,autoSuffix:l.naming.autoSuffix??(e=>e.format??c.format)};u=`${a(n(e,r(e,{flowId:e.flow?.flowId??``,jobId:e.flow?.jobId??``,nodeId:s,nodeType:`transcode`},{format:c.format}),t))}.${i}`}else u=o.replace(/\.[^.]+$/,`.${i}`);return{newType:t,newFileName:u}};return yield*i({id:s,name:`Transcode`,description:`Converts video to specified format and codec`,nodeTypeId:`transcode-video`,outputTypeId:e,keepOutput:l?.keepOutput,nodeType:`transcode`,namingVars:{format:c.format},mode:g,streamingConfig:_,transform:(e,t)=>o.map(u.transcode(e,c),e=>{let{newType:n,newFileName:r}=v(t);return{bytes:e,type:n,fileName:r}}),streamingTransform:u.transcodeStream?(e,t)=>o.gen(function*(){let n=u.transcodeStream;if(!n)throw Error(`transcodeStream not available`);let r=yield*n(e,c),{newType:i,newFileName:a}=v(t);return{stream:r,type:i,fileName:a}}):void 0})})}const g={fileSizeThreshold:1e7,chunkSize:1048576};function _(n,r,a){return o.gen(function*(){let c=yield*t;if(r.endTime!==void 0&&r.endTime<=r.startTime)return yield*s.fromCode(`VALIDATION_ERROR`,{body:`endTime must be greater than startTime`,details:{params:r}}).toEffect();if(r.duration!==void 0&&r.endTime!==void 0&&r.duration!==r.endTime-r.startTime)return yield*s.fromCode(`VALIDATION_ERROR`,{body:`Cannot specify both endTime and duration with conflicting values`,details:{params:r}}).toEffect();if(r.duration!==void 0&&r.duration<=0)return yield*s.fromCode(`VALIDATION_ERROR`,{body:`duration must be greater than 0`,details:{params:r}}).toEffect();let l=c.supportsStreaming??!1,u=a?.mode??`auto`,d=u===`buffered`?`buffered`:l?u:`buffered`,f={...g,...a?.streamingConfig},p=a?.naming?{...a.naming,autoSuffix:a.naming.autoSuffix??(()=>`trimmed`)}:void 0;return yield*i({id:n,name:`Trim Video`,description:`Extracts a segment from the video`,nodeTypeId:`trim-video`,outputTypeId:e,keepOutput:a?.keepOutput,naming:p,nodeType:`trim`,mode:d,streamingConfig:f,transform:e=>o.map(c.trim(e,r),e=>({bytes:e})),streamingTransform:c.trimStream?e=>o.gen(function*(){let t=c.trimStream;if(!t)throw Error(`trimStream not available`);return{stream:yield*t(e,r)}}):void 0})})}export{c as createDescribeVideoNode,h as createTranscodeVideoNode,_ as createTrimVideoNode,u as createVideoResizeNode,d as createVideoThumbnailNode};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["namingConfig: FileNamingConfig | undefined","newFileName: string | undefined","namingConfig: FileNamingConfig","formatToMimeType: Record<TranscodeVideoParams[\"format\"], string>","formatToExtension: Record<TranscodeVideoParams[\"format\"], string>","newFileName: string | undefined","namingConfig: FileNamingConfig","namingConfig: FileNamingConfig | undefined"],"sources":["../src/describe-video-node.ts","../src/resize-node.ts","../src/thumbnail-node.ts","../src/transcode-node.ts","../src/trim-node.ts"],"sourcesContent":["import {\n createTransformNode,\n STORAGE_OUTPUT_TYPE_ID,\n VideoPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\n\n/**\n * Creates a Describe Video metadata extraction node\n *\n * Extracts comprehensive metadata from the video file including duration,\n * resolution, codec, bitrate, and audio information. The metadata is stored\n * in the file context for downstream nodes while passing through the original bytes.\n *\n * @param id - Unique node identifier\n * @returns Effect that resolves to the configured node\n *\n * @example\n * ```typescript\n * const node = yield* createDescribeVideoNode(\"describe-1\");\n * ```\n */\nexport function createDescribeVideoNode(\n id: string,\n options?: { keepOutput?: boolean },\n) {\n return Effect.gen(function* () {\n const videoService = yield* VideoPlugin;\n\n return yield* createTransformNode({\n id,\n name: \"Describe Video\",\n description:\n \"Extracts video metadata (duration, resolution, codec, etc.)\",\n nodeTypeId: \"describe-video\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n transform: (inputBytes, file) =>\n Effect.gen(function* () {\n // Extract metadata\n const metadata = yield* videoService.describe(inputBytes);\n\n // Store metadata in file context for downstream nodes\n return {\n bytes: inputBytes, // Pass through original bytes unchanged\n metadata: {\n ...file.metadata,\n videoInfo: metadata,\n },\n };\n }),\n });\n });\n}\n","import {\n createTransformNode,\n type FileNamingConfig,\n type ResizeVideoParams,\n STORAGE_OUTPUT_TYPE_ID,\n VideoPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\n\n/**\n * Creates a Resize video processing node\n *\n * Changes video resolution while optionally maintaining aspect ratio.\n *\n * @param id - Unique node identifier\n * @param params - Resize parameters\n * @param options - Optional configuration\n * @param options.keepOutput - Whether to keep output in flow results\n * @param options.naming - File naming configuration (auto suffix: `${width}x${height}`)\n *\n * @example\n * ```typescript\n * // With auto-naming: \"video.mp4\" -> \"video-1280x720.mp4\"\n * const node = yield* createVideoResizeNode(\"resize-1\", {\n * width: 1280,\n * height: 720,\n * aspectRatio: \"keep\",\n * scaling: \"bicubic\"\n * }, {\n * naming: { mode: \"auto\" }\n * });\n * ```\n */\nexport function createVideoResizeNode(\n id: string,\n params: ResizeVideoParams,\n options?: { keepOutput?: boolean; naming?: FileNamingConfig },\n) {\n return Effect.gen(function* () {\n const videoService = yield* VideoPlugin;\n\n // Build naming config with auto suffix for video resize\n const namingConfig: FileNamingConfig | undefined = options?.naming\n ? {\n ...options.naming,\n autoSuffix:\n options.naming.autoSuffix ??\n ((ctx) => `${ctx.width ?? params.width}x${ctx.height ?? params.height}`),\n }\n : undefined;\n\n return yield* createTransformNode({\n id,\n name: \"Resize Video\",\n description: \"Changes video resolution\",\n nodeTypeId: \"resize-video\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n naming: namingConfig,\n nodeType: \"resize-video\",\n namingVars: { width: params.width, height: params.height },\n transform: (inputBytes, _file) =>\n Effect.map(videoService.resize(inputBytes, params), (resizedBytes) => {\n // Pass through video bytes (no metadata changes needed)\n return {\n bytes: resizedBytes,\n };\n }),\n });\n });\n}\n","import {\n applyFileNaming,\n buildNamingContext,\n createTransformNode,\n type ExtractFrameVideoParams,\n type FileNamingConfig,\n getBaseName,\n STORAGE_OUTPUT_TYPE_ID,\n VideoPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\n\n/**\n * Creates a Thumbnail generation node\n *\n * Extracts a single frame from the video as an image (JPEG or PNG).\n *\n * @param id - Unique node identifier\n * @param params - Frame extraction parameters\n * @param options - Optional configuration\n * @param options.keepOutput - Whether to keep output in flow results\n * @param options.naming - File naming configuration (auto suffix: `thumb`)\n *\n * @example\n * ```typescript\n * // With auto-naming: \"video.mp4\" -> \"video-thumb.jpg\"\n * const node = yield* createVideoThumbnailNode(\"thumbnail-1\", {\n * timestamp: 15,\n * format: \"jpeg\",\n * quality: 85\n * }, {\n * naming: { mode: \"auto\" }\n * });\n * ```\n */\nexport function createVideoThumbnailNode(\n id: string,\n params: ExtractFrameVideoParams,\n options?: { keepOutput?: boolean; naming?: FileNamingConfig },\n) {\n return Effect.gen(function* () {\n const videoService = yield* VideoPlugin;\n\n const format = params.format || \"jpeg\";\n\n return yield* createTransformNode({\n id,\n name: \"Generate Thumbnail\",\n description: \"Extracts a frame from video as an image\",\n nodeTypeId: \"thumbnail-video\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n // Note: naming is handled in transform since format changes extension\n nodeType: \"thumbnail\",\n namingVars: { format },\n transform: (inputBytes, file) =>\n Effect.map(\n videoService.extractFrame(inputBytes, params),\n (imageBytes) => {\n // Map output to image MIME type and extension\n const mimeType = format === \"png\" ? \"image/png\" : \"image/jpeg\";\n const extension = format === \"png\" ? \"png\" : \"jpg\";\n\n // Get original fileName\n const fileName = file.metadata?.fileName;\n let newFileName: string | undefined;\n\n if (fileName && typeof fileName === \"string\") {\n // Apply naming if configured\n if (options?.naming) {\n const namingConfig: FileNamingConfig = {\n ...options.naming,\n autoSuffix: options.naming.autoSuffix ?? (() => \"thumb\"),\n };\n const namingContext = buildNamingContext(\n file,\n {\n flowId: file.flow?.flowId ?? \"\",\n jobId: file.flow?.jobId ?? \"\",\n nodeId: id,\n nodeType: \"thumbnail\",\n },\n { format },\n );\n // Apply naming to get base name with suffix\n const namedFile = applyFileNaming(file, namingContext, namingConfig);\n // Replace extension with image extension\n newFileName = `${getBaseName(namedFile)}.${extension}`;\n } else {\n // No naming config, just update extension\n newFileName = fileName.replace(/\\.[^.]+$/, `.${extension}`);\n }\n }\n\n return {\n bytes: imageBytes,\n type: mimeType,\n fileName: newFileName,\n };\n },\n ),\n });\n });\n}\n","import {\n applyFileNaming,\n buildNamingContext,\n createTransformNode,\n type FileNamingConfig,\n getBaseName,\n STORAGE_OUTPUT_TYPE_ID,\n type TranscodeVideoParams,\n VideoPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\n\n// Map video format to MIME type\nconst formatToMimeType: Record<TranscodeVideoParams[\"format\"], string> = {\n mp4: \"video/mp4\",\n webm: \"video/webm\",\n mov: \"video/quicktime\",\n avi: \"video/x-msvideo\",\n};\n\n// Map video format to file extension\nconst formatToExtension: Record<TranscodeVideoParams[\"format\"], string> = {\n mp4: \"mp4\",\n webm: \"webm\",\n mov: \"mov\",\n avi: \"avi\",\n};\n\n/**\n * Creates a Transcode video processing node\n *\n * Converts video to specified format and codec, optionally adjusting bitrates.\n *\n * @param id - Unique node identifier\n * @param params - Transcode parameters\n * @param options - Optional configuration\n * @param options.keepOutput - Whether to keep output in flow results\n * @param options.naming - File naming configuration (auto suffix: `${format}`)\n *\n * @example\n * ```typescript\n * // With auto-naming: \"video.mov\" -> \"video-webm.webm\"\n * const node = yield* createTranscodeVideoNode(\"transcode-1\", {\n * format: \"webm\",\n * codec: \"vp9\",\n * videoBitrate: \"1000k\"\n * }, {\n * naming: { mode: \"auto\" }\n * });\n * ```\n */\nexport function createTranscodeVideoNode(\n id: string,\n params: TranscodeVideoParams,\n options?: { keepOutput?: boolean; naming?: FileNamingConfig },\n) {\n return Effect.gen(function* () {\n const videoService = yield* VideoPlugin;\n\n return yield* createTransformNode({\n id,\n name: \"Transcode\",\n description: \"Converts video to specified format and codec\",\n nodeTypeId: \"transcode-video\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n // Note: naming is handled in transform since format changes extension\n nodeType: \"transcode\",\n namingVars: { format: params.format },\n transform: (inputBytes, file) =>\n Effect.map(\n videoService.transcode(inputBytes, params),\n (transcodedBytes) => {\n // Update metadata if format changes\n const newType = formatToMimeType[params.format];\n const newExtension = formatToExtension[params.format];\n\n // Get original fileName\n const fileName = file.metadata?.fileName;\n let newFileName: string | undefined;\n\n if (fileName && typeof fileName === \"string\") {\n // Apply naming if configured\n if (options?.naming) {\n const namingConfig: FileNamingConfig = {\n ...options.naming,\n autoSuffix:\n options.naming.autoSuffix ?? ((ctx) => ctx.format ?? params.format),\n };\n const namingContext = buildNamingContext(\n file,\n {\n flowId: file.flow?.flowId ?? \"\",\n jobId: file.flow?.jobId ?? \"\",\n nodeId: id,\n nodeType: \"transcode\",\n },\n { format: params.format },\n );\n // Apply naming to get base name with suffix\n const namedFile = applyFileNaming(file, namingContext, namingConfig);\n // Replace extension with new format extension\n newFileName = `${getBaseName(namedFile)}.${newExtension}`;\n } else {\n // No naming config, just update extension\n newFileName = fileName.replace(/\\.[^.]+$/, `.${newExtension}`);\n }\n }\n\n return {\n bytes: transcodedBytes,\n type: newType,\n fileName: newFileName,\n };\n },\n ),\n });\n });\n}\n","import { UploadistaError } from \"@uploadista/core/errors\";\nimport {\n createTransformNode,\n type FileNamingConfig,\n STORAGE_OUTPUT_TYPE_ID,\n type TrimVideoParams,\n VideoPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\n\n/**\n * Creates a Trim video processing node\n *\n * Extracts a segment from the video by time range.\n *\n * @param id - Unique node identifier\n * @param params - Trim parameters\n * @param options - Optional configuration\n * @param options.keepOutput - Whether to keep output in flow results\n * @param options.naming - File naming configuration (auto suffix: `trimmed`)\n *\n * @example\n * ```typescript\n * // With auto-naming: \"video.mp4\" -> \"video-trimmed.mp4\"\n * const node = yield* createTrimVideoNode(\"trim-1\", {\n * startTime: 10,\n * endTime: 30\n * }, {\n * naming: { mode: \"auto\" }\n * });\n * ```\n */\nexport function createTrimVideoNode(\n id: string,\n params: TrimVideoParams,\n options?: { keepOutput?: boolean; naming?: FileNamingConfig },\n) {\n return Effect.gen(function* () {\n const videoService = yield* VideoPlugin;\n\n // Validate params\n if (params.endTime !== undefined && params.endTime <= params.startTime) {\n return yield* UploadistaError.fromCode(\"VALIDATION_ERROR\", {\n body: \"endTime must be greater than startTime\",\n details: { params },\n }).toEffect();\n }\n\n if (\n params.duration !== undefined &&\n params.endTime !== undefined &&\n params.duration !== params.endTime - params.startTime\n ) {\n return yield* UploadistaError.fromCode(\"VALIDATION_ERROR\", {\n body: \"Cannot specify both endTime and duration with conflicting values\",\n details: { params },\n }).toEffect();\n }\n\n if (params.duration !== undefined && params.duration <= 0) {\n return yield* UploadistaError.fromCode(\"VALIDATION_ERROR\", {\n body: \"duration must be greater than 0\",\n details: { params },\n }).toEffect();\n }\n\n // Build naming config with auto suffix for trim\n const namingConfig: FileNamingConfig | undefined = options?.naming\n ? {\n ...options.naming,\n autoSuffix: options.naming.autoSuffix ?? (() => \"trimmed\"),\n }\n : undefined;\n\n return yield* createTransformNode({\n id,\n name: \"Trim Video\",\n description: \"Extracts a segment from the video\",\n nodeTypeId: \"trim-video\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n naming: namingConfig,\n nodeType: \"trim\",\n transform: (inputBytes, _file) =>\n Effect.map(videoService.trim(inputBytes, params), (trimmedBytes) => {\n // Pass through video bytes\n return {\n bytes: trimmedBytes,\n };\n }),\n });\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["DEFAULT_VIDEO_STREAMING_CONFIG: StreamingConfig","effectiveMode: TransformMode","streamingConfig: StreamingConfig","DEFAULT_VIDEO_STREAMING_CONFIG","namingConfig: FileNamingConfig | undefined","newFileName: string | undefined","namingConfig: FileNamingConfig","DEFAULT_VIDEO_STREAMING_CONFIG: StreamingConfig","formatToMimeType: Record<TranscodeVideoParams[\"format\"], string>","formatToExtension: Record<TranscodeVideoParams[\"format\"], string>","effectiveMode: TransformMode","streamingConfig: StreamingConfig","DEFAULT_VIDEO_STREAMING_CONFIG","newFileName: string | undefined","namingConfig: FileNamingConfig","DEFAULT_VIDEO_STREAMING_CONFIG: StreamingConfig","effectiveMode: TransformMode","streamingConfig: StreamingConfig","namingConfig: FileNamingConfig | undefined"],"sources":["../src/describe-video-node.ts","../src/resize-node.ts","../src/thumbnail-node.ts","../src/transcode-node.ts","../src/trim-node.ts"],"sourcesContent":["import {\n createTransformNode,\n STORAGE_OUTPUT_TYPE_ID,\n VideoPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\n\n/**\n * Creates a Describe Video metadata extraction node\n *\n * Extracts comprehensive metadata from the video file including duration,\n * resolution, codec, bitrate, and audio information. The metadata is stored\n * in the file context for downstream nodes while passing through the original bytes.\n *\n * @param id - Unique node identifier\n * @returns Effect that resolves to the configured node\n *\n * @example\n * ```typescript\n * const node = yield* createDescribeVideoNode(\"describe-1\");\n * ```\n */\nexport function createDescribeVideoNode(\n id: string,\n options?: { keepOutput?: boolean },\n) {\n return Effect.gen(function* () {\n const videoService = yield* VideoPlugin;\n\n return yield* createTransformNode({\n id,\n name: \"Describe Video\",\n description:\n \"Extracts video metadata (duration, resolution, codec, etc.)\",\n nodeTypeId: \"describe-video\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n transform: (inputBytes, file) =>\n Effect.gen(function* () {\n // Extract metadata\n const metadata = yield* videoService.describe(inputBytes);\n\n // Store metadata in file context for downstream nodes\n return {\n bytes: inputBytes, // Pass through original bytes unchanged\n metadata: {\n ...file.metadata,\n videoInfo: metadata,\n },\n };\n }),\n });\n });\n}\n","import {\n createTransformNode,\n type FileNamingConfig,\n type ResizeVideoParams,\n STORAGE_OUTPUT_TYPE_ID,\n type StreamingConfig,\n type TransformMode,\n VideoPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\n\n// Default streaming config for video processing\nconst DEFAULT_VIDEO_STREAMING_CONFIG: StreamingConfig = {\n fileSizeThreshold: 10_000_000, // 10MB threshold for video\n chunkSize: 1_048_576, // 1MB chunks\n};\n\n/**\n * Creates a Resize video processing node\n *\n * Changes video resolution while optionally maintaining aspect ratio.\n *\n * Supports both buffered and streaming modes for memory-efficient processing\n * of large videos. In streaming mode, the output is streamed directly to storage,\n * reducing peak memory usage.\n *\n * @param id - Unique node identifier\n * @param params - Resize parameters\n * @param options - Optional configuration\n * @param options.keepOutput - Whether to keep output in flow results\n * @param options.naming - File naming configuration (auto suffix: `${width}x${height}`)\n * @param options.mode - Transform mode: \"buffered\", \"streaming\", or \"auto\" (default)\n * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)\n *\n * @example\n * ```typescript\n * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered\n * const node = yield* createVideoResizeNode(\"resize-1\", {\n * width: 1280,\n * height: 720,\n * aspectRatio: \"keep\",\n * scaling: \"bicubic\"\n * }, {\n * naming: { mode: \"auto\" }\n * });\n *\n * // Force buffered mode for small files\n * const nodeBuffered = yield* createVideoResizeNode(\"resize-2\", {\n * width: 1920,\n * height: 1080\n * }, {\n * mode: \"buffered\",\n * naming: { mode: \"auto\" }\n * });\n *\n * // Force streaming mode for memory efficiency\n * const nodeStreaming = yield* createVideoResizeNode(\"resize-3\", {\n * width: 1280,\n * height: 720\n * }, {\n * mode: \"streaming\",\n * naming: { mode: \"auto\" }\n * });\n * ```\n */\nexport function createVideoResizeNode(\n id: string,\n params: ResizeVideoParams,\n options?: {\n keepOutput?: boolean;\n naming?: FileNamingConfig;\n mode?: TransformMode;\n streamingConfig?: StreamingConfig;\n },\n) {\n return Effect.gen(function* () {\n const videoService = yield* VideoPlugin;\n\n // Determine if streaming is available and requested\n const supportsStreaming = videoService.supportsStreaming ?? false;\n const requestedMode = options?.mode ?? \"auto\";\n\n // If streaming requested but not supported, fall back to buffered\n const effectiveMode: TransformMode =\n requestedMode === \"buffered\"\n ? \"buffered\"\n : supportsStreaming\n ? requestedMode\n : \"buffered\";\n\n // Use video-specific streaming config as default\n const streamingConfig: StreamingConfig = {\n ...DEFAULT_VIDEO_STREAMING_CONFIG,\n ...options?.streamingConfig,\n };\n\n // Build naming config with auto suffix for video resize\n const namingConfig: FileNamingConfig | undefined = options?.naming\n ? {\n ...options.naming,\n autoSuffix:\n options.naming.autoSuffix ??\n ((ctx) => `${ctx.width ?? params.width}x${ctx.height ?? params.height}`),\n }\n : undefined;\n\n return yield* createTransformNode({\n id,\n name: \"Resize Video\",\n description: \"Changes video resolution\",\n nodeTypeId: \"resize-video\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n naming: namingConfig,\n nodeType: \"resize-video\",\n namingVars: { width: params.width, height: params.height },\n mode: effectiveMode,\n streamingConfig,\n // Buffered transform\n transform: (inputBytes) =>\n Effect.map(videoService.resize(inputBytes, params), (resizedBytes) => ({\n bytes: resizedBytes,\n })),\n // Streaming transform\n streamingTransform: videoService.resizeStream\n ? (inputStream) =>\n Effect.gen(function* () {\n const resizeStreamFn = videoService.resizeStream;\n if (!resizeStreamFn) {\n throw new Error(\"resizeStream not available\");\n }\n const outputStream = yield* resizeStreamFn(inputStream, params);\n return { stream: outputStream };\n })\n : undefined,\n });\n });\n}\n","import {\n applyFileNaming,\n buildNamingContext,\n createTransformNode,\n type ExtractFrameVideoParams,\n type FileNamingConfig,\n getBaseName,\n STORAGE_OUTPUT_TYPE_ID,\n VideoPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\n\n/**\n * Creates a Thumbnail generation node\n *\n * Extracts a single frame from the video as an image (JPEG or PNG).\n *\n * @param id - Unique node identifier\n * @param params - Frame extraction parameters\n * @param options - Optional configuration\n * @param options.keepOutput - Whether to keep output in flow results\n * @param options.naming - File naming configuration (auto suffix: `thumb`)\n *\n * @example\n * ```typescript\n * // With auto-naming: \"video.mp4\" -> \"video-thumb.jpg\"\n * const node = yield* createVideoThumbnailNode(\"thumbnail-1\", {\n * timestamp: 15,\n * format: \"jpeg\",\n * quality: 85\n * }, {\n * naming: { mode: \"auto\" }\n * });\n * ```\n */\nexport function createVideoThumbnailNode(\n id: string,\n params: ExtractFrameVideoParams,\n options?: { keepOutput?: boolean; naming?: FileNamingConfig },\n) {\n return Effect.gen(function* () {\n const videoService = yield* VideoPlugin;\n\n const format = params.format || \"jpeg\";\n\n return yield* createTransformNode({\n id,\n name: \"Generate Thumbnail\",\n description: \"Extracts a frame from video as an image\",\n nodeTypeId: \"thumbnail-video\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n // Note: naming is handled in transform since format changes extension\n nodeType: \"thumbnail\",\n namingVars: { format },\n transform: (inputBytes, file) =>\n Effect.map(\n videoService.extractFrame(inputBytes, params),\n (imageBytes) => {\n // Map output to image MIME type and extension\n const mimeType = format === \"png\" ? \"image/png\" : \"image/jpeg\";\n const extension = format === \"png\" ? \"png\" : \"jpg\";\n\n // Get original fileName\n const fileName = file.metadata?.fileName;\n let newFileName: string | undefined;\n\n if (fileName && typeof fileName === \"string\") {\n // Apply naming if configured\n if (options?.naming) {\n const namingConfig: FileNamingConfig = {\n ...options.naming,\n autoSuffix: options.naming.autoSuffix ?? (() => \"thumb\"),\n };\n const namingContext = buildNamingContext(\n file,\n {\n flowId: file.flow?.flowId ?? \"\",\n jobId: file.flow?.jobId ?? \"\",\n nodeId: id,\n nodeType: \"thumbnail\",\n },\n { format },\n );\n // Apply naming to get base name with suffix\n const namedFile = applyFileNaming(file, namingContext, namingConfig);\n // Replace extension with image extension\n newFileName = `${getBaseName(namedFile)}.${extension}`;\n } else {\n // No naming config, just update extension\n newFileName = fileName.replace(/\\.[^.]+$/, `.${extension}`);\n }\n }\n\n return {\n bytes: imageBytes,\n type: mimeType,\n fileName: newFileName,\n };\n },\n ),\n });\n });\n}\n","import {\n applyFileNaming,\n buildNamingContext,\n createTransformNode,\n type FileNamingConfig,\n getBaseName,\n STORAGE_OUTPUT_TYPE_ID,\n type StreamingConfig,\n type TranscodeVideoParams,\n type TransformMode,\n VideoPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\n\n// Default streaming config for video processing\nconst DEFAULT_VIDEO_STREAMING_CONFIG: StreamingConfig = {\n fileSizeThreshold: 10_000_000, // 10MB threshold for video\n chunkSize: 1_048_576, // 1MB chunks\n};\n\n// Map video format to MIME type\nconst formatToMimeType: Record<TranscodeVideoParams[\"format\"], string> = {\n mp4: \"video/mp4\",\n webm: \"video/webm\",\n mov: \"video/quicktime\",\n avi: \"video/x-msvideo\",\n};\n\n// Map video format to file extension\nconst formatToExtension: Record<TranscodeVideoParams[\"format\"], string> = {\n mp4: \"mp4\",\n webm: \"webm\",\n mov: \"mov\",\n avi: \"avi\",\n};\n\n/**\n * Creates a Transcode video processing node\n *\n * Converts video to specified format and codec, optionally adjusting bitrates.\n *\n * Supports both buffered and streaming modes for memory-efficient processing\n * of large videos. In streaming mode, the output is streamed directly to storage,\n * reducing peak memory usage.\n *\n * @param id - Unique node identifier\n * @param params - Transcode parameters\n * @param options - Optional configuration\n * @param options.keepOutput - Whether to keep output in flow results\n * @param options.naming - File naming configuration (auto suffix: `${format}`)\n * @param options.mode - Transform mode: \"buffered\", \"streaming\", or \"auto\" (default)\n * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)\n *\n * @example\n * ```typescript\n * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered\n * const node = yield* createTranscodeVideoNode(\"transcode-1\", {\n * format: \"webm\",\n * codec: \"vp9\",\n * videoBitrate: \"1000k\"\n * }, {\n * naming: { mode: \"auto\" }\n * });\n *\n * // Force buffered mode for small files\n * const nodeBuffered = yield* createTranscodeVideoNode(\"transcode-2\", {\n * format: \"mp4\",\n * codec: \"h264\"\n * }, {\n * mode: \"buffered\",\n * naming: { mode: \"auto\" }\n * });\n *\n * // Force streaming mode for memory efficiency\n * const nodeStreaming = yield* createTranscodeVideoNode(\"transcode-3\", {\n * format: \"mp4\",\n * codec: \"h264\"\n * }, {\n * mode: \"streaming\",\n * naming: { mode: \"auto\" }\n * });\n * ```\n */\nexport function createTranscodeVideoNode(\n id: string,\n params: TranscodeVideoParams,\n options?: {\n keepOutput?: boolean;\n naming?: FileNamingConfig;\n mode?: TransformMode;\n streamingConfig?: StreamingConfig;\n },\n) {\n return Effect.gen(function* () {\n const videoService = yield* VideoPlugin;\n\n // Determine if streaming is available and requested\n const supportsStreaming = videoService.supportsStreaming ?? false;\n const requestedMode = options?.mode ?? \"auto\";\n\n // If streaming requested but not supported, fall back to buffered\n const effectiveMode: TransformMode =\n requestedMode === \"buffered\"\n ? \"buffered\"\n : supportsStreaming\n ? requestedMode\n : \"buffered\";\n\n // Use video-specific streaming config as default\n const streamingConfig: StreamingConfig = {\n ...DEFAULT_VIDEO_STREAMING_CONFIG,\n ...options?.streamingConfig,\n };\n\n // Helper to build output metadata\n const buildOutputMetadata = (file: {\n metadata?: Record<string, unknown>;\n flow?: { flowId?: string; jobId?: string };\n }) => {\n const newType = formatToMimeType[params.format];\n const newExtension = formatToExtension[params.format];\n\n const fileName = file.metadata?.fileName;\n let newFileName: string | undefined;\n\n if (fileName && typeof fileName === \"string\") {\n if (options?.naming) {\n const namingConfig: FileNamingConfig = {\n ...options.naming,\n autoSuffix:\n options.naming.autoSuffix ?? ((ctx) => ctx.format ?? params.format),\n };\n const namingContext = buildNamingContext(\n file as Parameters<typeof buildNamingContext>[0],\n {\n flowId: file.flow?.flowId ?? \"\",\n jobId: file.flow?.jobId ?? \"\",\n nodeId: id,\n nodeType: \"transcode\",\n },\n { format: params.format },\n );\n const namedFile = applyFileNaming(\n file as Parameters<typeof applyFileNaming>[0],\n namingContext,\n namingConfig,\n );\n newFileName = `${getBaseName(namedFile)}.${newExtension}`;\n } else {\n newFileName = fileName.replace(/\\.[^.]+$/, `.${newExtension}`);\n }\n }\n\n return { newType, newFileName };\n };\n\n return yield* createTransformNode({\n id,\n name: \"Transcode\",\n description: \"Converts video to specified format and codec\",\n nodeTypeId: \"transcode-video\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n nodeType: \"transcode\",\n namingVars: { format: params.format },\n mode: effectiveMode,\n streamingConfig,\n // Buffered transform\n transform: (inputBytes, file) =>\n Effect.map(\n videoService.transcode(inputBytes, params),\n (transcodedBytes) => {\n const { newType, newFileName } = buildOutputMetadata(file);\n return {\n bytes: transcodedBytes,\n type: newType,\n fileName: newFileName,\n };\n },\n ),\n // Streaming transform\n streamingTransform: videoService.transcodeStream\n ? (inputStream, file) =>\n Effect.gen(function* () {\n const transcodeStreamFn = videoService.transcodeStream;\n if (!transcodeStreamFn) {\n throw new Error(\"transcodeStream not available\");\n }\n const outputStream = yield* transcodeStreamFn(inputStream, params);\n const { newType, newFileName } = buildOutputMetadata(file);\n return {\n stream: outputStream,\n type: newType,\n fileName: newFileName,\n };\n })\n : undefined,\n });\n });\n}\n","import { UploadistaError } from \"@uploadista/core/errors\";\nimport {\n createTransformNode,\n type FileNamingConfig,\n STORAGE_OUTPUT_TYPE_ID,\n type StreamingConfig,\n type TransformMode,\n type TrimVideoParams,\n VideoPlugin,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\n\n// Default streaming config for video processing\nconst DEFAULT_VIDEO_STREAMING_CONFIG: StreamingConfig = {\n fileSizeThreshold: 10_000_000, // 10MB threshold for video\n chunkSize: 1_048_576, // 1MB chunks\n};\n\n/**\n * Creates a Trim video processing node\n *\n * Extracts a segment from the video by time range.\n *\n * Supports both buffered and streaming modes for memory-efficient processing\n * of large videos. In streaming mode, the output is streamed directly to storage,\n * reducing peak memory usage.\n *\n * @param id - Unique node identifier\n * @param params - Trim parameters\n * @param options - Optional configuration\n * @param options.keepOutput - Whether to keep output in flow results\n * @param options.naming - File naming configuration (auto suffix: `trimmed`)\n * @param options.mode - Transform mode: \"buffered\", \"streaming\", or \"auto\" (default)\n * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)\n *\n * @example\n * ```typescript\n * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered\n * const node = yield* createTrimVideoNode(\"trim-1\", {\n * startTime: 10,\n * endTime: 30\n * }, {\n * naming: { mode: \"auto\" }\n * });\n *\n * // Force buffered mode for small files\n * const nodeBuffered = yield* createTrimVideoNode(\"trim-2\", {\n * startTime: 0,\n * duration: 60\n * }, {\n * mode: \"buffered\",\n * naming: { mode: \"auto\" }\n * });\n *\n * // Force streaming mode for memory efficiency\n * const nodeStreaming = yield* createTrimVideoNode(\"trim-3\", {\n * startTime: 5,\n * endTime: 25\n * }, {\n * mode: \"streaming\",\n * naming: { mode: \"auto\" }\n * });\n * ```\n */\nexport function createTrimVideoNode(\n id: string,\n params: TrimVideoParams,\n options?: {\n keepOutput?: boolean;\n naming?: FileNamingConfig;\n mode?: TransformMode;\n streamingConfig?: StreamingConfig;\n },\n) {\n return Effect.gen(function* () {\n const videoService = yield* VideoPlugin;\n\n // Validate params\n if (params.endTime !== undefined && params.endTime <= params.startTime) {\n return yield* UploadistaError.fromCode(\"VALIDATION_ERROR\", {\n body: \"endTime must be greater than startTime\",\n details: { params },\n }).toEffect();\n }\n\n if (\n params.duration !== undefined &&\n params.endTime !== undefined &&\n params.duration !== params.endTime - params.startTime\n ) {\n return yield* UploadistaError.fromCode(\"VALIDATION_ERROR\", {\n body: \"Cannot specify both endTime and duration with conflicting values\",\n details: { params },\n }).toEffect();\n }\n\n if (params.duration !== undefined && params.duration <= 0) {\n return yield* UploadistaError.fromCode(\"VALIDATION_ERROR\", {\n body: \"duration must be greater than 0\",\n details: { params },\n }).toEffect();\n }\n\n // Determine if streaming is available and requested\n const supportsStreaming = videoService.supportsStreaming ?? false;\n const requestedMode = options?.mode ?? \"auto\";\n\n // If streaming requested but not supported, fall back to buffered\n const effectiveMode: TransformMode =\n requestedMode === \"buffered\"\n ? \"buffered\"\n : supportsStreaming\n ? requestedMode\n : \"buffered\";\n\n // Use video-specific streaming config as default\n const streamingConfig: StreamingConfig = {\n ...DEFAULT_VIDEO_STREAMING_CONFIG,\n ...options?.streamingConfig,\n };\n\n // Build naming config with auto suffix for trim\n const namingConfig: FileNamingConfig | undefined = options?.naming\n ? {\n ...options.naming,\n autoSuffix: options.naming.autoSuffix ?? (() => \"trimmed\"),\n }\n : undefined;\n\n return yield* createTransformNode({\n id,\n name: \"Trim Video\",\n description: \"Extracts a segment from the video\",\n nodeTypeId: \"trim-video\",\n outputTypeId: STORAGE_OUTPUT_TYPE_ID,\n keepOutput: options?.keepOutput,\n naming: namingConfig,\n nodeType: \"trim\",\n mode: effectiveMode,\n streamingConfig,\n // Buffered transform\n transform: (inputBytes) =>\n Effect.map(videoService.trim(inputBytes, params), (trimmedBytes) => ({\n bytes: trimmedBytes,\n })),\n // Streaming transform\n streamingTransform: videoService.trimStream\n ? (inputStream) =>\n Effect.gen(function* () {\n const trimStreamFn = videoService.trimStream;\n if (!trimStreamFn) {\n throw new Error(\"trimStream not available\");\n }\n const outputStream = yield* trimStreamFn(inputStream, params);\n return { stream: outputStream };\n })\n : undefined,\n });\n });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uploadista/flow-videos-nodes",
3
3
  "type": "module",
4
- "version": "0.0.19",
4
+ "version": "0.0.20-beta.2",
5
5
  "description": "Video processing nodes for Uploadista Flow",
6
6
  "license": "MIT",
7
7
  "author": "Uploadista",
@@ -14,7 +14,7 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@uploadista/core": "0.0.19"
17
+ "@uploadista/core": "0.0.20-beta.2"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "effect": "^3.0.0",
@@ -23,11 +23,11 @@
23
23
  "devDependencies": {
24
24
  "@effect/vitest": "0.27.0",
25
25
  "@types/node": "24.10.1",
26
- "effect": "3.19.8",
27
- "tsdown": "0.16.8",
26
+ "effect": "3.19.9",
27
+ "tsdown": "0.17.1",
28
28
  "vitest": "4.0.15",
29
29
  "zod": "4.1.13",
30
- "@uploadista/typescript-config": "0.0.19"
30
+ "@uploadista/typescript-config": "0.0.20-beta.2"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsdown",
@@ -3,24 +3,38 @@ import {
3
3
  type FileNamingConfig,
4
4
  type ResizeVideoParams,
5
5
  STORAGE_OUTPUT_TYPE_ID,
6
+ type StreamingConfig,
7
+ type TransformMode,
6
8
  VideoPlugin,
7
9
  } from "@uploadista/core/flow";
8
10
  import { Effect } from "effect";
9
11
 
12
+ // Default streaming config for video processing
13
+ const DEFAULT_VIDEO_STREAMING_CONFIG: StreamingConfig = {
14
+ fileSizeThreshold: 10_000_000, // 10MB threshold for video
15
+ chunkSize: 1_048_576, // 1MB chunks
16
+ };
17
+
10
18
  /**
11
19
  * Creates a Resize video processing node
12
20
  *
13
21
  * Changes video resolution while optionally maintaining aspect ratio.
14
22
  *
23
+ * Supports both buffered and streaming modes for memory-efficient processing
24
+ * of large videos. In streaming mode, the output is streamed directly to storage,
25
+ * reducing peak memory usage.
26
+ *
15
27
  * @param id - Unique node identifier
16
28
  * @param params - Resize parameters
17
29
  * @param options - Optional configuration
18
30
  * @param options.keepOutput - Whether to keep output in flow results
19
31
  * @param options.naming - File naming configuration (auto suffix: `${width}x${height}`)
32
+ * @param options.mode - Transform mode: "buffered", "streaming", or "auto" (default)
33
+ * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)
20
34
  *
21
35
  * @example
22
36
  * ```typescript
23
- * // With auto-naming: "video.mp4" -> "video-1280x720.mp4"
37
+ * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered
24
38
  * const node = yield* createVideoResizeNode("resize-1", {
25
39
  * width: 1280,
26
40
  * height: 720,
@@ -29,16 +43,57 @@ import { Effect } from "effect";
29
43
  * }, {
30
44
  * naming: { mode: "auto" }
31
45
  * });
46
+ *
47
+ * // Force buffered mode for small files
48
+ * const nodeBuffered = yield* createVideoResizeNode("resize-2", {
49
+ * width: 1920,
50
+ * height: 1080
51
+ * }, {
52
+ * mode: "buffered",
53
+ * naming: { mode: "auto" }
54
+ * });
55
+ *
56
+ * // Force streaming mode for memory efficiency
57
+ * const nodeStreaming = yield* createVideoResizeNode("resize-3", {
58
+ * width: 1280,
59
+ * height: 720
60
+ * }, {
61
+ * mode: "streaming",
62
+ * naming: { mode: "auto" }
63
+ * });
32
64
  * ```
33
65
  */
34
66
  export function createVideoResizeNode(
35
67
  id: string,
36
68
  params: ResizeVideoParams,
37
- options?: { keepOutput?: boolean; naming?: FileNamingConfig },
69
+ options?: {
70
+ keepOutput?: boolean;
71
+ naming?: FileNamingConfig;
72
+ mode?: TransformMode;
73
+ streamingConfig?: StreamingConfig;
74
+ },
38
75
  ) {
39
76
  return Effect.gen(function* () {
40
77
  const videoService = yield* VideoPlugin;
41
78
 
79
+ // Determine if streaming is available and requested
80
+ const supportsStreaming = videoService.supportsStreaming ?? false;
81
+ const requestedMode = options?.mode ?? "auto";
82
+
83
+ // If streaming requested but not supported, fall back to buffered
84
+ const effectiveMode: TransformMode =
85
+ requestedMode === "buffered"
86
+ ? "buffered"
87
+ : supportsStreaming
88
+ ? requestedMode
89
+ : "buffered";
90
+
91
+ // Use video-specific streaming config as default
92
+ const streamingConfig: StreamingConfig = {
93
+ ...DEFAULT_VIDEO_STREAMING_CONFIG,
94
+ ...options?.streamingConfig,
95
+ };
96
+
42
97
  // Build naming config with auto suffix for video resize
43
98
  const namingConfig: FileNamingConfig | undefined = options?.naming
44
99
  ? {
@@ -59,13 +114,25 @@ export function createVideoResizeNode(
59
114
  naming: namingConfig,
60
115
  nodeType: "resize-video",
61
116
  namingVars: { width: params.width, height: params.height },
62
- transform: (inputBytes, _file) =>
63
- Effect.map(videoService.resize(inputBytes, params), (resizedBytes) => {
64
- // Pass through video bytes (no metadata changes needed)
65
- return {
66
- bytes: resizedBytes,
67
- };
68
- }),
117
+ mode: effectiveMode,
118
+ streamingConfig,
119
+ // Buffered transform
120
+ transform: (inputBytes) =>
121
+ Effect.map(videoService.resize(inputBytes, params), (resizedBytes) => ({
122
+ bytes: resizedBytes,
123
+ })),
124
+ // Streaming transform
125
+ streamingTransform: videoService.resizeStream
126
+ ? (inputStream) =>
127
+ Effect.gen(function* () {
128
+ const resizeStreamFn = videoService.resizeStream;
129
+ if (!resizeStreamFn) {
130
+ throw new Error("resizeStream not available");
131
+ }
132
+ const outputStream = yield* resizeStreamFn(inputStream, params);
133
+ return { stream: outputStream };
134
+ })
135
+ : undefined,
69
136
  });
70
137
  });
71
138
  }
@@ -5,11 +5,19 @@ import {
5
5
  type FileNamingConfig,
6
6
  getBaseName,
7
7
  STORAGE_OUTPUT_TYPE_ID,
8
+ type StreamingConfig,
8
9
  type TranscodeVideoParams,
10
+ type TransformMode,
9
11
  VideoPlugin,
10
12
  } from "@uploadista/core/flow";
11
13
  import { Effect } from "effect";
12
14
 
15
+ // Default streaming config for video processing
16
+ const DEFAULT_VIDEO_STREAMING_CONFIG: StreamingConfig = {
17
+ fileSizeThreshold: 10_000_000, // 10MB threshold for video
18
+ chunkSize: 1_048_576, // 1MB chunks
19
+ };
20
+
13
21
  // Map video format to MIME type
14
22
  const formatToMimeType: Record<TranscodeVideoParams["format"], string> = {
15
23
  mp4: "video/mp4",
@@ -31,15 +39,21 @@ const formatToExtension: Record<TranscodeVideoParams["format"], string> = {
31
39
  *
32
40
  * Converts video to specified format and codec, optionally adjusting bitrates.
33
41
  *
42
+ * Supports both buffered and streaming modes for memory-efficient processing
43
+ * of large videos. In streaming mode, the output is streamed directly to storage,
44
+ * reducing peak memory usage.
45
+ *
34
46
  * @param id - Unique node identifier
35
47
  * @param params - Transcode parameters
36
48
  * @param options - Optional configuration
37
49
  * @param options.keepOutput - Whether to keep output in flow results
38
50
  * @param options.naming - File naming configuration (auto suffix: `${format}`)
51
+ * @param options.mode - Transform mode: "buffered", "streaming", or "auto" (default)
52
+ * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)
39
53
  *
40
54
  * @example
41
55
  * ```typescript
42
- * // With auto-naming: "video.mov" -> "video-webm.webm"
56
+ * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered
43
57
  * const node = yield* createTranscodeVideoNode("transcode-1", {
44
58
  * format: "webm",
45
59
  * codec: "vp9",
@@ -47,16 +61,99 @@ const formatToExtension: Record<TranscodeVideoParams["format"], string> = {
47
61
  * }, {
48
62
  * naming: { mode: "auto" }
49
63
  * });
64
+ *
65
+ * // Force buffered mode for small files
66
+ * const nodeBuffered = yield* createTranscodeVideoNode("transcode-2", {
67
+ * format: "mp4",
68
+ * codec: "h264"
69
+ * }, {
70
+ * mode: "buffered",
71
+ * naming: { mode: "auto" }
72
+ * });
73
+ *
74
+ * // Force streaming mode for memory efficiency
75
+ * const nodeStreaming = yield* createTranscodeVideoNode("transcode-3", {
76
+ * format: "mp4",
77
+ * codec: "h264"
78
+ * }, {
79
+ * mode: "streaming",
80
+ * naming: { mode: "auto" }
81
+ * });
50
82
  * ```
51
83
  */
52
84
  export function createTranscodeVideoNode(
53
85
  id: string,
54
86
  params: TranscodeVideoParams,
55
- options?: { keepOutput?: boolean; naming?: FileNamingConfig },
87
+ options?: {
88
+ keepOutput?: boolean;
89
+ naming?: FileNamingConfig;
90
+ mode?: TransformMode;
91
+ streamingConfig?: StreamingConfig;
92
+ },
56
93
  ) {
57
94
  return Effect.gen(function* () {
58
95
  const videoService = yield* VideoPlugin;
59
96
 
97
+ // Determine if streaming is available and requested
98
+ const supportsStreaming = videoService.supportsStreaming ?? false;
99
+ const requestedMode = options?.mode ?? "auto";
100
+
101
+ // If streaming requested but not supported, fall back to buffered
102
+ const effectiveMode: TransformMode =
103
+ requestedMode === "buffered"
104
+ ? "buffered"
105
+ : supportsStreaming
106
+ ? requestedMode
107
+ : "buffered";
108
+
109
+ // Use video-specific streaming config as default
110
+ const streamingConfig: StreamingConfig = {
111
+ ...DEFAULT_VIDEO_STREAMING_CONFIG,
112
+ ...options?.streamingConfig,
113
+ };
114
+
115
+ // Helper to build output metadata
116
+ const buildOutputMetadata = (file: {
117
+ metadata?: Record<string, unknown>;
118
+ flow?: { flowId?: string; jobId?: string };
119
+ }) => {
120
+ const newType = formatToMimeType[params.format];
121
+ const newExtension = formatToExtension[params.format];
122
+
123
+ const fileName = file.metadata?.fileName;
124
+ let newFileName: string | undefined;
125
+
126
+ if (fileName && typeof fileName === "string") {
127
+ if (options?.naming) {
128
+ const namingConfig: FileNamingConfig = {
129
+ ...options.naming,
130
+ autoSuffix:
131
+ options.naming.autoSuffix ?? ((ctx) => ctx.format ?? params.format),
132
+ };
133
+ const namingContext = buildNamingContext(
134
+ file as Parameters<typeof buildNamingContext>[0],
135
+ {
136
+ flowId: file.flow?.flowId ?? "",
137
+ jobId: file.flow?.jobId ?? "",
138
+ nodeId: id,
139
+ nodeType: "transcode",
140
+ },
141
+ { format: params.format },
142
+ );
143
+ const namedFile = applyFileNaming(
144
+ file as Parameters<typeof applyFileNaming>[0],
145
+ namingContext,
146
+ namingConfig,
147
+ );
148
+ newFileName = `${getBaseName(namedFile)}.${newExtension}`;
149
+ } else {
150
+ newFileName = fileName.replace(/\.[^.]+$/, `.${newExtension}`);
151
+ }
152
+ }
153
+
154
+ return { newType, newFileName };
155
+ };
156
+
60
157
  return yield* createTransformNode({
61
158
  id,
62
159
  name: "Transcode",
@@ -64,49 +161,16 @@ export function createTranscodeVideoNode(
64
161
  nodeTypeId: "transcode-video",
65
162
  outputTypeId: STORAGE_OUTPUT_TYPE_ID,
66
163
  keepOutput: options?.keepOutput,
67
- // Note: naming is handled in transform since format changes extension
68
164
  nodeType: "transcode",
69
165
  namingVars: { format: params.format },
166
+ mode: effectiveMode,
167
+ streamingConfig,
168
+ // Buffered transform
70
169
  transform: (inputBytes, file) =>
71
170
  Effect.map(
72
171
  videoService.transcode(inputBytes, params),
73
172
  (transcodedBytes) => {
74
- // Update metadata if format changes
75
- const newType = formatToMimeType[params.format];
76
- const newExtension = formatToExtension[params.format];
77
-
78
- // Get original fileName
79
- const fileName = file.metadata?.fileName;
80
- let newFileName: string | undefined;
81
-
82
- if (fileName && typeof fileName === "string") {
83
- // Apply naming if configured
84
- if (options?.naming) {
85
- const namingConfig: FileNamingConfig = {
86
- ...options.naming,
87
- autoSuffix:
88
- options.naming.autoSuffix ?? ((ctx) => ctx.format ?? params.format),
89
- };
90
- const namingContext = buildNamingContext(
91
- file,
92
- {
93
- flowId: file.flow?.flowId ?? "",
94
- jobId: file.flow?.jobId ?? "",
95
- nodeId: id,
96
- nodeType: "transcode",
97
- },
98
- { format: params.format },
99
- );
100
- // Apply naming to get base name with suffix
101
- const namedFile = applyFileNaming(file, namingContext, namingConfig);
102
- // Replace extension with new format extension
103
- newFileName = `${getBaseName(namedFile)}.${newExtension}`;
104
- } else {
105
- // No naming config, just update extension
106
- newFileName = fileName.replace(/\.[^.]+$/, `.${newExtension}`);
107
- }
108
- }
109
-
173
+ const { newType, newFileName } = buildOutputMetadata(file);
110
174
  return {
111
175
  bytes: transcodedBytes,
112
176
  type: newType,
@@ -114,6 +178,23 @@ export function createTranscodeVideoNode(
114
178
  };
115
179
  },
116
180
  ),
181
+ // Streaming transform
182
+ streamingTransform: videoService.transcodeStream
183
+ ? (inputStream, file) =>
184
+ Effect.gen(function* () {
185
+ const transcodeStreamFn = videoService.transcodeStream;
186
+ if (!transcodeStreamFn) {
187
+ throw new Error("transcodeStream not available");
188
+ }
189
+ const outputStream = yield* transcodeStreamFn(inputStream, params);
190
+ const { newType, newFileName } = buildOutputMetadata(file);
191
+ return {
192
+ stream: outputStream,
193
+ type: newType,
194
+ fileName: newFileName,
195
+ };
196
+ })
197
+ : undefined,
117
198
  });
118
199
  });
119
200
  }
package/src/trim-node.ts CHANGED
@@ -3,37 +3,74 @@ import {
3
3
  createTransformNode,
4
4
  type FileNamingConfig,
5
5
  STORAGE_OUTPUT_TYPE_ID,
6
+ type StreamingConfig,
7
+ type TransformMode,
6
8
  type TrimVideoParams,
7
9
  VideoPlugin,
8
10
  } from "@uploadista/core/flow";
9
11
  import { Effect } from "effect";
10
12
 
13
+ // Default streaming config for video processing
14
+ const DEFAULT_VIDEO_STREAMING_CONFIG: StreamingConfig = {
15
+ fileSizeThreshold: 10_000_000, // 10MB threshold for video
16
+ chunkSize: 1_048_576, // 1MB chunks
17
+ };
18
+
11
19
  /**
12
20
  * Creates a Trim video processing node
13
21
  *
14
22
  * Extracts a segment from the video by time range.
15
23
  *
24
+ * Supports both buffered and streaming modes for memory-efficient processing
25
+ * of large videos. In streaming mode, the output is streamed directly to storage,
26
+ * reducing peak memory usage.
27
+ *
16
28
  * @param id - Unique node identifier
17
29
  * @param params - Trim parameters
18
30
  * @param options - Optional configuration
19
31
  * @param options.keepOutput - Whether to keep output in flow results
20
32
  * @param options.naming - File naming configuration (auto suffix: `trimmed`)
33
+ * @param options.mode - Transform mode: "buffered", "streaming", or "auto" (default)
34
+ * @param options.streamingConfig - Streaming configuration (file size threshold, chunk size)
21
35
  *
22
36
  * @example
23
37
  * ```typescript
24
- * // With auto-naming: "video.mp4" -> "video-trimmed.mp4"
38
+ * // Auto mode (default) - uses streaming for files > 10MB, otherwise buffered
25
39
  * const node = yield* createTrimVideoNode("trim-1", {
26
40
  * startTime: 10,
27
41
  * endTime: 30
28
42
  * }, {
29
43
  * naming: { mode: "auto" }
30
44
  * });
45
+ *
46
+ * // Force buffered mode for small files
47
+ * const nodeBuffered = yield* createTrimVideoNode("trim-2", {
48
+ * startTime: 0,
49
+ * duration: 60
50
+ * }, {
51
+ * mode: "buffered",
52
+ * naming: { mode: "auto" }
53
+ * });
54
+ *
55
+ * // Force streaming mode for memory efficiency
56
+ * const nodeStreaming = yield* createTrimVideoNode("trim-3", {
57
+ * startTime: 5,
58
+ * endTime: 25
59
+ * }, {
60
+ * mode: "streaming",
61
+ * naming: { mode: "auto" }
62
+ * });
31
63
  * ```
32
64
  */
33
65
  export function createTrimVideoNode(
34
66
  id: string,
35
67
  params: TrimVideoParams,
36
- options?: { keepOutput?: boolean; naming?: FileNamingConfig },
68
+ options?: {
69
+ keepOutput?: boolean;
70
+ naming?: FileNamingConfig;
71
+ mode?: TransformMode;
72
+ streamingConfig?: StreamingConfig;
73
+ },
37
74
  ) {
38
75
  return Effect.gen(function* () {
39
76
  const videoService = yield* VideoPlugin;
@@ -64,6 +101,24 @@ export function createTrimVideoNode(
64
101
  }).toEffect();
65
102
  }
66
103
 
104
+ // Determine if streaming is available and requested
105
+ const supportsStreaming = videoService.supportsStreaming ?? false;
106
+ const requestedMode = options?.mode ?? "auto";
107
+
108
+ // If streaming requested but not supported, fall back to buffered
109
+ const effectiveMode: TransformMode =
110
+ requestedMode === "buffered"
111
+ ? "buffered"
112
+ : supportsStreaming
113
+ ? requestedMode
114
+ : "buffered";
115
+
116
+ // Use video-specific streaming config as default
117
+ const streamingConfig: StreamingConfig = {
118
+ ...DEFAULT_VIDEO_STREAMING_CONFIG,
119
+ ...options?.streamingConfig,
120
+ };
121
+
67
122
  // Build naming config with auto suffix for trim
68
123
  const namingConfig: FileNamingConfig | undefined = options?.naming
69
124
  ? {
@@ -81,13 +136,25 @@ export function createTrimVideoNode(
81
136
  keepOutput: options?.keepOutput,
82
137
  naming: namingConfig,
83
138
  nodeType: "trim",
84
- transform: (inputBytes, _file) =>
85
- Effect.map(videoService.trim(inputBytes, params), (trimmedBytes) => {
86
- // Pass through video bytes
87
- return {
88
- bytes: trimmedBytes,
89
- };
90
- }),
139
+ mode: effectiveMode,
140
+ streamingConfig,
141
+ // Buffered transform
142
+ transform: (inputBytes) =>
143
+ Effect.map(videoService.trim(inputBytes, params), (trimmedBytes) => ({
144
+ bytes: trimmedBytes,
145
+ })),
146
+ // Streaming transform
147
+ streamingTransform: videoService.trimStream
148
+ ? (inputStream) =>
149
+ Effect.gen(function* () {
150
+ const trimStreamFn = videoService.trimStream;
151
+ if (!trimStreamFn) {
152
+ throw new Error("trimStream not available");
153
+ }
154
+ const outputStream = yield* trimStreamFn(inputStream, params);
155
+ return { stream: outputStream };
156
+ })
157
+ : undefined,
91
158
  });
92
159
  });
93
160
  }