@uploadista/flow-videos-av-node 0.0.20-beta.8 → 0.0.20-beta.9

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,6 +1,6 @@
1
1
 
2
2
  
3
- > @uploadista/flow-videos-av-node@0.0.20-beta.7 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/videos/av-node
3
+ > @uploadista/flow-videos-av-node@0.0.20-beta.8 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/videos/av-node
4
4
  > tsc --noEmit && tsdown
5
5
 
6
6
  ℹ tsdown v0.18.0 powered by rolldown v1.0.0-beta.53
@@ -9,15 +9,15 @@
9
9
  ℹ tsconfig: tsconfig.json
10
10
  ℹ Build start
11
11
  ℹ Cleaning 7 files
12
- ℹ [CJS] dist/index.cjs 14.08 kB │ gzip: 3.35 kB
13
- ℹ [CJS] 1 files, total: 14.08 kB
12
+ ℹ [CJS] dist/index.cjs 14.07 kB │ gzip: 3.35 kB
13
+ ℹ [CJS] 1 files, total: 14.07 kB
14
14
  ℹ [ESM] dist/index.mjs 13.40 kB │ gzip: 3.36 kB
15
- ℹ [ESM] dist/index.mjs.map 61.55 kB │ gzip: 9.93 kB
15
+ ℹ [ESM] dist/index.mjs.map 61.55 kB │ gzip: 9.92 kB
16
16
  ℹ [ESM] dist/index.d.mts.map  0.88 kB │ gzip: 0.47 kB
17
- ℹ [ESM] dist/index.d.mts  3.39 kB │ gzip: 1.04 kB
18
- ℹ [ESM] 4 files, total: 79.23 kB
17
+ ℹ [ESM] dist/index.d.mts  3.37 kB │ gzip: 1.05 kB
18
+ ℹ [ESM] 4 files, total: 79.20 kB
19
+ ✔ Build complete in 4999ms
19
20
  ℹ [CJS] dist/index.d.cts.map 0.88 kB │ gzip: 0.47 kB
20
- ℹ [CJS] dist/index.d.cts 3.39 kB │ gzip: 1.04 kB
21
- ℹ [CJS] 2 files, total: 4.28 kB
22
- ✔ Build complete in 5878ms
23
- ✔ Build complete in 5879ms
21
+ ℹ [CJS] dist/index.d.cts 3.37 kB │ gzip: 1.05 kB
22
+ ℹ [CJS] 2 files, total: 4.25 kB
23
+ ✔ Build complete in 5012ms
package/dist/index.cjs CHANGED
@@ -1,3 +1,3 @@
1
- let e=require(`node-av/constants`),t=require(`@uploadista/core/errors`),n=require(`@uploadista/observability`),r=require(`effect`),i=require(`node-av/api`),a=require(`@uploadista/core/flow`);async function o(){try{return await import(`node-av`),{available:!0,version:`4.x`}}catch(e){return{available:!1,error:e instanceof Error?e.message:String(e)}}}const s={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},c={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`},l={h264:e.FF_ENCODER_LIBX264,h265:e.FF_ENCODER_LIBX265,vp9:e.FF_ENCODER_LIBVPX_VP9,av1:e.FF_ENCODER_LIBAOM_AV1},u={aac:e.FF_ENCODER_AAC,mp3:e.FF_ENCODER_LIBMP3LAME,opus:e.FF_ENCODER_LIBOPUS,vorbis:e.FF_ENCODER_LIBVORBIS},d={jpeg:e.FF_ENCODER_MJPEG,mjpeg:e.FF_ENCODER_MJPEG,png:e.FF_ENCODER_PNG};function f(){let e=[],t=0n;return{callbacks:{write:n=>(e.push(n),t+=BigInt(n.length),n.length),seek:(e,n)=>{switch(n){case 0:t=e;break;case 1:t+=e;break;case 2:t=e;break}return t}},getOutput:()=>{let t=e.reduce((e,t)=>e+t.length,0),n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return n}}}function p(){let e=[],n=!1,i=null,a=0n;return{callbacks:{write:t=>{let n=new Uint8Array(t);if(e.push(n),a+=BigInt(t.length),i){let e=i;i=null,e()}return t.length},seek:(e,t)=>{switch(t){case 0:a=e;break;case 1:a+=e;break;case 2:a=e;break}return a}},stream:r.Stream.async(a=>((async()=>{for(;;){for(;e.length>0;){let t=e.shift();t&&a.single(t)}if(n&&e.length===0){a.end();return}await new Promise(t=>{i=t,(e.length>0||n)&&(i=null,t())})}})().catch(e=>{a.fail(t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming output failed: ${e instanceof Error?e.message:String(e)}`,cause:e}))}),r.Effect.void)),finalize:()=>{if(n=!0,i){let e=i;i=null,e()}}}}function m(e){return r.Effect.gen(function*(){let t=[];yield*r.Stream.runForEach(e,e=>r.Effect.sync(()=>{t.push(e)}));let n=t.reduce((e,t)=>e+t.byteLength,0),i=new Uint8Array(n),a=0;for(let e of t)i.set(e,a),a+=e.byteLength;return i})}function h(e){if(!e)return!1;let t=e.toLowerCase();return t===`video/mp2t`||t===`video/mpeg-ts`||t===`video/mpegts`||t===`application/x-mpegts`}function g(e){return typeof e==`object`&&!!e&&`pipe`in e&&typeof e.pipe==`function`}function _(){return{describe:e=>r.Effect.tryPromise({try:async()=>{let t=Buffer.from(e);await using n=await i.Demuxer.open(t);let r=n.video(),a=n.audio();if(!r)throw Error(`No video stream found`);let o=r.codecpar,s=0;if(r.rFrameRate){let{num:e,den:t}=r.rFrameRate;s=t?e/t:e}let c=`unknown`;if(r.sampleAspectRatio){let{num:e,den:t}=r.sampleAspectRatio;c=`${e}:${t}`}return{duration:n.duration||0,width:o.width||0,height:o.height||0,codec:String(o.codecId)||`unknown`,format:n.formatName||`unknown`,bitrate:n.bitRate||0,frameRate:s,aspectRatio:c,hasAudio:!!a,audioCodec:a?.codecpar.codecId?String(a.codecpar.codecId):void 0,audioBitrate:a?.codecpar.bitRate?Number(a.codecpar.bitRate):void 0,size:e.byteLength}},catch:e=>t.UploadistaError.fromCode(`VIDEO_METADATA_EXTRACTION_FAILED`,{body:`Failed to extract video metadata: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe((0,n.withOperationSpan)(`video`,`describe`,{"video.input_size":e.byteLength})),transcode:(e,a)=>r.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:r}=f();await using o=await i.Demuxer.open(t),s=await i.Muxer.open(n,{format:a.format});let c=o.video();if(!c)throw Error(`No video stream found`);using d=await i.Decoder.create(c);let p=a.codec?l[a.codec]:l.h264;using m=await i.Encoder.create(p,{...a.videoBitrate&&{bitrate:a.videoBitrate}});let h=s.addStream(m);for await(using e of d.frames(o.packets(c.index))){if(!e)continue;await m.encode(e);let t=await m.receive();for(;t;)await s.writePacket(t,h),t.free(),t=await m.receive()}await m.flush();let g=await m.receive();for(;g;)await s.writePacket(g,h),g.free(),g=await m.receive();let _=o.audio();if(_){using e=await i.Decoder.create(_);let t=a.audioCodec?u[a.audioCodec]:u.aac;using n=await i.Encoder.create(t,{...a.audioBitrate&&{bitrate:a.audioBitrate}});let r=s.addStream(n);for await(using t of e.frames(o.packets(_.index))){if(!t)continue;await n.encode(t);let e=await n.receive();for(;e;)await s.writePacket(e,r),e.free(),e=await n.receive()}await n.flush();let c=await n.receive();for(;c;)await s.writePacket(c,r),c.free(),c=await n.receive()}return r()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Transcode failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe((0,n.withOperationSpan)(`video`,`transcode`,{"video.format":a.format,"video.codec":a.codec,"video.video_bitrate":a.videoBitrate,"video.audio_codec":a.audioCodec,"video.audio_bitrate":a.audioBitrate,"video.input_size":e.byteLength})),resize:(e,a)=>r.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:r}=f();await using o=await i.Demuxer.open(t),s=await i.Muxer.open(n,{format:`mp4`});let c=o.video();if(!c)throw Error(`No video stream found`);using d=await i.Decoder.create(c);if(!a.width&&!a.height)throw Error(`Either width or height must be specified`);using p=await i.Encoder.create(l.h264);let m=s.addStream(p);for await(using e of d.frames(o.packets(c.index))){if(!e)continue;await p.encode(e);let t=await p.receive();for(;t;)await s.writePacket(t,m),t.free(),t=await p.receive()}await p.flush();let h=await p.receive();for(;h;)await s.writePacket(h,m),h.free(),h=await p.receive();let g=o.audio();if(g){using e=await i.Decoder.create(g),t=await i.Encoder.create(u.aac);let n=s.addStream(t);for await(using r of e.frames(o.packets(g.index))){if(!r)continue;await t.encode(r);let e=await t.receive();for(;e;)await s.writePacket(e,n),e.free(),e=await t.receive()}await t.flush();let r=await t.receive();for(;r;)await s.writePacket(r,n),r.free(),r=await t.receive()}return r()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Resize failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe((0,n.withOperationSpan)(`video`,`resize`,{"video.width":a.width,"video.height":a.height,"video.input_size":e.byteLength})),trim:(e,a)=>r.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:r}=f();await using o=await i.Demuxer.open(t),s=await i.Muxer.open(n,{format:`mp4`});let c=o.video();if(!c)throw Error(`No video stream found`);let d;d=a.duration===void 0?a.endTime===void 0?o.duration||1/0:a.endTime:a.startTime+a.duration;using p=await i.Decoder.create(c),m=await i.Encoder.create(l.h264);let h=s.addStream(m);for await(using e of p.frames(o.packets(c.index))){if(!e)continue;let t=e.pts||0n,n=c.timeBase?c.timeBase.num/c.timeBase.den:1,r=Number(t)*n;if(r>=a.startTime&&r<d){await m.encode(e);let t=await m.receive();for(;t;)await s.writePacket(t,h),t.free(),t=await m.receive()}if(r>=d)break}await m.flush();let g=await m.receive();for(;g;)await s.writePacket(g,h),g.free(),g=await m.receive();let _=o.audio();if(_){using e=await i.Decoder.create(_),t=await i.Encoder.create(u.aac);let n=s.addStream(t);for await(using r of e.frames(o.packets(_.index))){if(!r)continue;let e=r.pts||0n,i=_.timeBase?_.timeBase.num/_.timeBase.den:1,o=Number(e)*i;if(o>=a.startTime&&o<d){await t.encode(r);let e=await t.receive();for(;e;)await s.writePacket(e,n),e.free(),e=await t.receive()}if(o>=d)break}await t.flush();let r=await t.receive();for(;r;)await s.writePacket(r,n),r.free(),r=await t.receive()}return r()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Trim failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe((0,n.withOperationSpan)(`video`,`trim`,{"video.start_time":a.startTime,"video.end_time":a.endTime,"video.duration":a.duration,"video.input_size":e.byteLength})),extractFrame:(e,a)=>r.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),n=a.format||`jpeg`;await using r=await i.Demuxer.open(t);let o=r.video();if(!o)throw Error(`No video stream found`);using s=await i.Decoder.create(o);let c=null,l=a.timestamp;for await(using e of s.frames(r.packets(o.index))){if(!e)continue;let t=e.pts||0n,r=o.timeBase?o.timeBase.num/o.timeBase.den:1;if(Number(t)*r>=l){let t=d[n]||d.jpeg;using r=await i.Encoder.create(t);await r.encode(e);let a=await r.receive();if(a?.data){c=new Uint8Array(a.data),a.free();break}}}if(!c)throw Error(`No frame found at timestamp ${l}`);return c},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Frame extraction failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe((0,n.withOperationSpan)(`video`,`extract-frame`,{"video.timestamp":a.timestamp,"video.format":a.format??`jpeg`,"video.input_size":e.byteLength})),supportsStreaming:!0,transcodeStream:(e,a,o)=>r.Effect.gen(function*(){let n=g(e)?yield*m(e):e,{callbacks:o,stream:s,finalize:c}=p(),d=r.Effect.tryPromise({try:async()=>{let e=Buffer.from(n);await using t=await i.Demuxer.open(e),r=await i.Muxer.open(o,{format:a.format});let s=t.video();if(!s)throw Error(`No video stream found`);using c=await i.Decoder.create(s);let d=a.codec?l[a.codec]:l.h264;using f=await i.Encoder.create(d,{...a.videoBitrate&&{bitrate:a.videoBitrate}});let p=r.addStream(f);for await(using e of c.frames(t.packets(s.index))){if(!e)continue;await f.encode(e);let t=await f.receive();for(;t;)await r.writePacket(t,p),t.free(),t=await f.receive()}await f.flush();let m=await f.receive();for(;m;)await r.writePacket(m,p),m.free(),m=await f.receive();let h=t.audio();if(h){using e=await i.Decoder.create(h);let n=a.audioCodec?u[a.audioCodec]:u.aac;using o=await i.Encoder.create(n,{...a.audioBitrate&&{bitrate:a.audioBitrate}});let s=r.addStream(o);for await(using n of e.frames(t.packets(h.index))){if(!n)continue;await o.encode(n);let e=await o.receive();for(;e;)await r.writePacket(e,s),e.free(),e=await o.receive()}await o.flush();let c=await o.receive();for(;c;)await r.writePacket(c,s),c.free(),c=await o.receive()}},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming transcode failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*r.Effect.fork(r.Effect.tap(d,()=>r.Effect.sync(c))),s}).pipe((0,n.withOperationSpan)(`video`,`transcode-stream`,{"video.format":a.format,"video.codec":a.codec,"video.streaming_input":h(o?.inputFormat)})),resizeStream:(e,a,o)=>r.Effect.gen(function*(){let n=g(e)?yield*m(e):e,{callbacks:o,stream:s,finalize:c}=p(),d=r.Effect.tryPromise({try:async()=>{let e=Buffer.from(n);await using t=await i.Demuxer.open(e),r=await i.Muxer.open(o,{format:`mp4`});let s=t.video();if(!s)throw Error(`No video stream found`);if(!a.width&&!a.height)throw Error(`Either width or height must be specified`);using c=await i.Decoder.create(s),d=await i.Encoder.create(l.h264);let f=r.addStream(d);for await(using e of c.frames(t.packets(s.index))){if(!e)continue;await d.encode(e);let t=await d.receive();for(;t;)await r.writePacket(t,f),t.free(),t=await d.receive()}await d.flush();let p=await d.receive();for(;p;)await r.writePacket(p,f),p.free(),p=await d.receive();let m=t.audio();if(m){using e=await i.Decoder.create(m),n=await i.Encoder.create(u.aac);let a=r.addStream(n);for await(using i of e.frames(t.packets(m.index))){if(!i)continue;await n.encode(i);let e=await n.receive();for(;e;)await r.writePacket(e,a),e.free(),e=await n.receive()}await n.flush();let o=await n.receive();for(;o;)await r.writePacket(o,a),o.free(),o=await n.receive()}},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming resize failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*r.Effect.fork(r.Effect.tap(d,()=>r.Effect.sync(c))),s}).pipe((0,n.withOperationSpan)(`video`,`resize-stream`,{"video.width":a.width,"video.height":a.height,"video.streaming_input":h(o?.inputFormat)})),trimStream:(e,a,o)=>r.Effect.gen(function*(){let n=g(e)?yield*m(e):e,{callbacks:o,stream:s,finalize:c}=p(),d=r.Effect.tryPromise({try:async()=>{let e=Buffer.from(n);await using t=await i.Demuxer.open(e),r=await i.Muxer.open(o,{format:`mp4`});let s=t.video();if(!s)throw Error(`No video stream found`);let c;c=a.duration===void 0?a.endTime===void 0?t.duration||1/0:a.endTime:a.startTime+a.duration;using d=await i.Decoder.create(s),f=await i.Encoder.create(l.h264);let p=r.addStream(f);for await(using e of d.frames(t.packets(s.index))){if(!e)continue;let t=e.pts||0n,n=s.timeBase?s.timeBase.num/s.timeBase.den:1,i=Number(t)*n;if(i>=a.startTime&&i<c){await f.encode(e);let t=await f.receive();for(;t;)await r.writePacket(t,p),t.free(),t=await f.receive()}if(i>=c)break}await f.flush();let m=await f.receive();for(;m;)await r.writePacket(m,p),m.free(),m=await f.receive();let h=t.audio();if(h){using e=await i.Decoder.create(h),n=await i.Encoder.create(u.aac);let o=r.addStream(n);for await(using i of e.frames(t.packets(h.index))){if(!i)continue;let e=i.pts||0n,t=h.timeBase?h.timeBase.num/h.timeBase.den:1,s=Number(e)*t;if(s>=a.startTime&&s<c){await n.encode(i);let e=await n.receive();for(;e;)await r.writePacket(e,o),e.free(),e=await n.receive()}if(s>=c)break}await n.flush();let s=await n.receive();for(;s;)await r.writePacket(s,o),s.free(),s=await n.receive()}},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming trim failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*r.Effect.fork(r.Effect.tap(d,()=>r.Effect.sync(c))),s}).pipe((0,n.withOperationSpan)(`video`,`trim-stream`,{"video.start_time":a.startTime,"video.end_time":a.endTime,"video.duration":a.duration,"video.streaming_input":h(o?.inputFormat)}))}}const v=r.Layer.succeed(a.VideoPlugin,_()),y=r.Layer.effectDiscard(r.Effect.gen(function*(){let e=yield*r.Effect.promise(()=>o());e.available?console.log(`✓ node-av ${e.version} detected`):console.warn(`⚠️ node-av is not installed or not available.`,`
1
+ let e=require(`node-av/constants`),t=require(`@uploadista/core/errors`),n=require(`@uploadista/observability`),r=require(`effect`),i=require(`node-av/api`),a=require(`@uploadista/core/flow`);async function o(){try{return await import(`node-av`),{available:!0,version:`4.x`}}catch(e){return{available:!1,error:e instanceof Error?e.message:String(e)}}}const s={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},c={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`},l={h264:e.FF_ENCODER_LIBX264,h265:e.FF_ENCODER_LIBX265,vp9:e.FF_ENCODER_LIBVPX_VP9,av1:e.FF_ENCODER_LIBAOM_AV1},u={aac:e.FF_ENCODER_AAC,mp3:e.FF_ENCODER_LIBMP3LAME,opus:e.FF_ENCODER_LIBOPUS,vorbis:e.FF_ENCODER_LIBVORBIS},d={jpeg:e.FF_ENCODER_MJPEG,mjpeg:e.FF_ENCODER_MJPEG,png:e.FF_ENCODER_PNG};function f(){let e=[],t=0n;return{callbacks:{write:n=>(e.push(n),t+=BigInt(n.length),n.length),seek:(e,n)=>{switch(n){case 0:t=e;break;case 1:t+=e;break;case 2:t=e;break}return t}},getOutput:()=>{let t=e.reduce((e,t)=>e+t.length,0),n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return n}}}function p(){let e=[],n=!1,i=null,a=0n;return{callbacks:{write:t=>{let n=new Uint8Array(t);if(e.push(n),a+=BigInt(t.length),i){let e=i;i=null,e()}return t.length},seek:(e,t)=>{switch(t){case 0:a=e;break;case 1:a+=e;break;case 2:a=e;break}return a}},stream:r.Stream.async(a=>((async()=>{for(;;){for(;e.length>0;){let t=e.shift();t&&a.single(t)}if(n&&e.length===0){a.end();return}await new Promise(t=>{i=t,(e.length>0||n)&&(i=null,t())})}})().catch(e=>{a.fail(t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming output failed: ${e instanceof Error?e.message:String(e)}`,cause:e}))}),r.Effect.void)),finalize:()=>{if(n=!0,i){let e=i;i=null,e()}}}}function m(e){return r.Effect.gen(function*(){let t=[];yield*r.Stream.runForEach(e,e=>r.Effect.sync(()=>{t.push(e)}));let n=t.reduce((e,t)=>e+t.byteLength,0),i=new Uint8Array(n),a=0;for(let e of t)i.set(e,a),a+=e.byteLength;return i})}function h(e){if(!e)return!1;let t=e.toLowerCase();return t===`video/mp2t`||t===`video/mpeg-ts`||t===`video/mpegts`||t===`application/x-mpegts`}function g(e){return typeof e==`object`&&!!e&&`pipe`in e&&typeof e.pipe==`function`}function _(){return{describe:e=>r.Effect.tryPromise({try:async()=>{let t=Buffer.from(e);await using n=await i.Demuxer.open(t);let r=n.video(),a=n.audio();if(!r)throw Error(`No video stream found`);let o=r.codecpar,s=0;if(r.rFrameRate){let{num:e,den:t}=r.rFrameRate;s=t?e/t:e}let c=`unknown`;if(r.sampleAspectRatio){let{num:e,den:t}=r.sampleAspectRatio;c=`${e}:${t}`}return{duration:n.duration||0,width:o.width||0,height:o.height||0,codec:String(o.codecId)||`unknown`,format:n.formatName||`unknown`,bitrate:n.bitRate||0,frameRate:s,aspectRatio:c,hasAudio:!!a,audioCodec:a?.codecpar.codecId?String(a.codecpar.codecId):void 0,audioBitrate:a?.codecpar.bitRate?Number(a.codecpar.bitRate):void 0,size:e.byteLength}},catch:e=>t.UploadistaError.fromCode(`VIDEO_METADATA_EXTRACTION_FAILED`,{body:`Failed to extract video metadata: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe((0,n.withOperationSpan)(`video`,`describe`,{"video.input_size":e.byteLength})),transcode:(e,a)=>r.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:r}=f();await using o=await i.Demuxer.open(t),s=await i.Muxer.open(n,{format:a.format});let c=o.video();if(!c)throw Error(`No video stream found`);using d=await i.Decoder.create(c);let p=a.codec?l[a.codec]:l.h264;using m=await i.Encoder.create(p,{...a.videoBitrate&&{bitrate:a.videoBitrate}});let h=s.addStream(m);for await(using e of d.frames(o.packets(c.index))){if(!e)continue;await m.encode(e);let t=await m.receive();for(;t;)await s.writePacket(t,h),t.free(),t=await m.receive()}await m.flush();let g=await m.receive();for(;g;)await s.writePacket(g,h),g.free(),g=await m.receive();let _=o.audio();if(_){using e=await i.Decoder.create(_);let t=a.audioCodec?u[a.audioCodec]:u.aac;using n=await i.Encoder.create(t,{...a.audioBitrate&&{bitrate:a.audioBitrate}});let r=s.addStream(n);for await(using t of e.frames(o.packets(_.index))){if(!t)continue;await n.encode(t);let e=await n.receive();for(;e;)await s.writePacket(e,r),e.free(),e=await n.receive()}await n.flush();let c=await n.receive();for(;c;)await s.writePacket(c,r),c.free(),c=await n.receive()}return r()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Transcode failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe((0,n.withOperationSpan)(`video`,`transcode`,{"video.format":a.format,"video.codec":a.codec,"video.video_bitrate":a.videoBitrate,"video.audio_codec":a.audioCodec,"video.audio_bitrate":a.audioBitrate,"video.input_size":e.byteLength})),resize:(e,a)=>r.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:r}=f();await using o=await i.Demuxer.open(t),s=await i.Muxer.open(n,{format:`mp4`});let c=o.video();if(!c)throw Error(`No video stream found`);using d=await i.Decoder.create(c);if(!a.width&&!a.height)throw Error(`Either width or height must be specified`);using p=await i.Encoder.create(l.h264);let m=s.addStream(p);for await(using e of d.frames(o.packets(c.index))){if(!e)continue;await p.encode(e);let t=await p.receive();for(;t;)await s.writePacket(t,m),t.free(),t=await p.receive()}await p.flush();let h=await p.receive();for(;h;)await s.writePacket(h,m),h.free(),h=await p.receive();let g=o.audio();if(g){using e=await i.Decoder.create(g),t=await i.Encoder.create(u.aac);let n=s.addStream(t);for await(using r of e.frames(o.packets(g.index))){if(!r)continue;await t.encode(r);let e=await t.receive();for(;e;)await s.writePacket(e,n),e.free(),e=await t.receive()}await t.flush();let r=await t.receive();for(;r;)await s.writePacket(r,n),r.free(),r=await t.receive()}return r()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Resize failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe((0,n.withOperationSpan)(`video`,`resize`,{"video.width":a.width,"video.height":a.height,"video.input_size":e.byteLength})),trim:(e,a)=>r.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:r}=f();await using o=await i.Demuxer.open(t),s=await i.Muxer.open(n,{format:`mp4`});let c=o.video();if(!c)throw Error(`No video stream found`);let d;d=a.duration===void 0?a.endTime===void 0?o.duration||1/0:a.endTime:a.startTime+a.duration;using p=await i.Decoder.create(c),m=await i.Encoder.create(l.h264);let h=s.addStream(m);for await(using e of p.frames(o.packets(c.index))){if(!e)continue;let t=e.pts||0n,n=c.timeBase?c.timeBase.num/c.timeBase.den:1,r=Number(t)*n;if(r>=a.startTime&&r<d){await m.encode(e);let t=await m.receive();for(;t;)await s.writePacket(t,h),t.free(),t=await m.receive()}if(r>=d)break}await m.flush();let g=await m.receive();for(;g;)await s.writePacket(g,h),g.free(),g=await m.receive();let _=o.audio();if(_){using e=await i.Decoder.create(_),t=await i.Encoder.create(u.aac);let n=s.addStream(t);for await(using r of e.frames(o.packets(_.index))){if(!r)continue;let e=r.pts||0n,i=_.timeBase?_.timeBase.num/_.timeBase.den:1,o=Number(e)*i;if(o>=a.startTime&&o<d){await t.encode(r);let e=await t.receive();for(;e;)await s.writePacket(e,n),e.free(),e=await t.receive()}if(o>=d)break}await t.flush();let r=await t.receive();for(;r;)await s.writePacket(r,n),r.free(),r=await t.receive()}return r()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Trim failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe((0,n.withOperationSpan)(`video`,`trim`,{"video.start_time":a.startTime,"video.end_time":a.endTime,"video.duration":a.duration,"video.input_size":e.byteLength})),extractFrame:(e,a)=>r.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),n=a.format||`jpeg`;await using r=await i.Demuxer.open(t);let o=r.video();if(!o)throw Error(`No video stream found`);using s=await i.Decoder.create(o);let c=null,l=a.timestamp;for await(using e of s.frames(r.packets(o.index))){if(!e)continue;let t=e.pts||0n,r=o.timeBase?o.timeBase.num/o.timeBase.den:1;if(Number(t)*r>=l){let t=d[n]||d.jpeg;using r=await i.Encoder.create(t);await r.encode(e);let a=await r.receive();if(a?.data){c=new Uint8Array(a.data),a.free();break}}}if(!c)throw Error(`No frame found at timestamp ${l}`);return c},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Frame extraction failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe((0,n.withOperationSpan)(`video`,`extract-frame`,{"video.timestamp":a.timestamp,"video.format":a.format??`jpeg`,"video.input_size":e.byteLength})),supportsStreaming:!0,transcodeStream:(e,a,o)=>r.Effect.gen(function*(){let n=g(e)?yield*m(e):e,{callbacks:o,stream:s,finalize:c}=p(),d=r.Effect.tryPromise({try:async()=>{let e=Buffer.from(n);await using t=await i.Demuxer.open(e),r=await i.Muxer.open(o,{format:a.format});let s=t.video();if(!s)throw Error(`No video stream found`);using c=await i.Decoder.create(s);let d=a.codec?l[a.codec]:l.h264;using f=await i.Encoder.create(d,{...a.videoBitrate&&{bitrate:a.videoBitrate}});let p=r.addStream(f);for await(using e of c.frames(t.packets(s.index))){if(!e)continue;await f.encode(e);let t=await f.receive();for(;t;)await r.writePacket(t,p),t.free(),t=await f.receive()}await f.flush();let m=await f.receive();for(;m;)await r.writePacket(m,p),m.free(),m=await f.receive();let h=t.audio();if(h){using e=await i.Decoder.create(h);let n=a.audioCodec?u[a.audioCodec]:u.aac;using o=await i.Encoder.create(n,{...a.audioBitrate&&{bitrate:a.audioBitrate}});let s=r.addStream(o);for await(using n of e.frames(t.packets(h.index))){if(!n)continue;await o.encode(n);let e=await o.receive();for(;e;)await r.writePacket(e,s),e.free(),e=await o.receive()}await o.flush();let c=await o.receive();for(;c;)await r.writePacket(c,s),c.free(),c=await o.receive()}},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming transcode failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*r.Effect.fork(r.Effect.tap(d,()=>r.Effect.sync(c))),s}).pipe((0,n.withOperationSpan)(`video`,`transcode-stream`,{"video.format":a.format,"video.codec":a.codec,"video.streaming_input":h(o?.inputFormat)})),resizeStream:(e,a,o)=>r.Effect.gen(function*(){let n=g(e)?yield*m(e):e,{callbacks:o,stream:s,finalize:c}=p(),d=r.Effect.tryPromise({try:async()=>{let e=Buffer.from(n);await using t=await i.Demuxer.open(e),r=await i.Muxer.open(o,{format:`mp4`});let s=t.video();if(!s)throw Error(`No video stream found`);if(!a.width&&!a.height)throw Error(`Either width or height must be specified`);using c=await i.Decoder.create(s),d=await i.Encoder.create(l.h264);let f=r.addStream(d);for await(using e of c.frames(t.packets(s.index))){if(!e)continue;await d.encode(e);let t=await d.receive();for(;t;)await r.writePacket(t,f),t.free(),t=await d.receive()}await d.flush();let p=await d.receive();for(;p;)await r.writePacket(p,f),p.free(),p=await d.receive();let m=t.audio();if(m){using e=await i.Decoder.create(m),n=await i.Encoder.create(u.aac);let a=r.addStream(n);for await(using i of e.frames(t.packets(m.index))){if(!i)continue;await n.encode(i);let e=await n.receive();for(;e;)await r.writePacket(e,a),e.free(),e=await n.receive()}await n.flush();let o=await n.receive();for(;o;)await r.writePacket(o,a),o.free(),o=await n.receive()}},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming resize failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*r.Effect.fork(r.Effect.tap(d,()=>r.Effect.sync(c))),s}).pipe((0,n.withOperationSpan)(`video`,`resize-stream`,{"video.width":a.width,"video.height":a.height,"video.streaming_input":h(o?.inputFormat)})),trimStream:(e,a,o)=>r.Effect.gen(function*(){let n=g(e)?yield*m(e):e,{callbacks:o,stream:s,finalize:c}=p(),d=r.Effect.tryPromise({try:async()=>{let e=Buffer.from(n);await using t=await i.Demuxer.open(e),r=await i.Muxer.open(o,{format:`mp4`});let s=t.video();if(!s)throw Error(`No video stream found`);let c;c=a.duration===void 0?a.endTime===void 0?t.duration||1/0:a.endTime:a.startTime+a.duration;using d=await i.Decoder.create(s),f=await i.Encoder.create(l.h264);let p=r.addStream(f);for await(using e of d.frames(t.packets(s.index))){if(!e)continue;let t=e.pts||0n,n=s.timeBase?s.timeBase.num/s.timeBase.den:1,i=Number(t)*n;if(i>=a.startTime&&i<c){await f.encode(e);let t=await f.receive();for(;t;)await r.writePacket(t,p),t.free(),t=await f.receive()}if(i>=c)break}await f.flush();let m=await f.receive();for(;m;)await r.writePacket(m,p),m.free(),m=await f.receive();let h=t.audio();if(h){using e=await i.Decoder.create(h),n=await i.Encoder.create(u.aac);let o=r.addStream(n);for await(using i of e.frames(t.packets(h.index))){if(!i)continue;let e=i.pts||0n,t=h.timeBase?h.timeBase.num/h.timeBase.den:1,s=Number(e)*t;if(s>=a.startTime&&s<c){await n.encode(i);let e=await n.receive();for(;e;)await r.writePacket(e,o),e.free(),e=await n.receive()}if(s>=c)break}await n.flush();let s=await n.receive();for(;s;)await r.writePacket(s,o),s.free(),s=await n.receive()}},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming trim failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*r.Effect.fork(r.Effect.tap(d,()=>r.Effect.sync(c))),s}).pipe((0,n.withOperationSpan)(`video`,`trim-stream`,{"video.start_time":a.startTime,"video.end_time":a.endTime,"video.duration":a.duration,"video.streaming_input":h(o?.inputFormat)}))}}const v=()=>r.Layer.succeed(a.VideoPlugin,_()),y=()=>r.Layer.effectDiscard(r.Effect.gen(function*(){let e=yield*r.Effect.promise(()=>o());e.available?console.log(`✓ node-av ${e.version} detected`):console.warn(`⚠️ node-av is not installed or not available.`,`
2
2
  Video processing operations will fail.`,`
3
- Install node-av: npm install node-av`)})).pipe(r.Layer.provideMerge(v));exports.AVNodeVideoPlugin=v,exports.AVNodeVideoPluginWithCheck=y,exports.audioCodecToAVName=u,exports.checkAVAvailable=o,exports.codecToAVName=l,exports.createAVNodeVideoPlugin=_,exports.formatToExtension=c,exports.formatToMimeType=s,exports.imageFormatToEncoder=d;
3
+ Install node-av: npm install node-av`)})).pipe(r.Layer.provideMerge(v()));exports.audioCodecToAVName=u,exports.checkAVAvailable=o,exports.codecToAVName=l,exports.createVideoPlugin=_,exports.formatToExtension=c,exports.formatToMimeType=s,exports.imageFormatToEncoder=d,exports.videoPlugin=v,exports.videoPluginWithCheck=y;
package/dist/index.d.cts CHANGED
@@ -43,7 +43,7 @@ declare const imageFormatToEncoder: Record<string, FFEncoderCodec>;
43
43
  /**
44
44
  * Creates a node-av based video processing plugin
45
45
  */
46
- declare function createAVNodeVideoPlugin(): VideoPluginShape;
46
+ declare function createVideoPlugin(): VideoPluginShape;
47
47
  //#endregion
48
48
  //#region src/video-plugin-layer.d.ts
49
49
  /**
@@ -69,7 +69,7 @@ declare function createAVNodeVideoPlugin(): VideoPluginShape;
69
69
  * );
70
70
  * ```
71
71
  */
72
- declare const AVNodeVideoPlugin: Layer.Layer<VideoPlugin, never, never>;
72
+ declare const videoPlugin: () => Layer.Layer<VideoPlugin, never, never>;
73
73
  /**
74
74
  * Effect Layer for the node-av video plugin with availability check
75
75
  *
@@ -93,7 +93,7 @@ declare const AVNodeVideoPlugin: Layer.Layer<VideoPlugin, never, never>;
93
93
  * );
94
94
  * ```
95
95
  */
96
- declare const AVNodeVideoPluginWithCheck: Layer.Layer<VideoPlugin, never, never>;
96
+ declare const videoPluginWithCheck: () => Layer.Layer<VideoPlugin, never, never>;
97
97
  //#endregion
98
- export { AVCheckResult, AVNodeVideoPlugin, AVNodeVideoPluginWithCheck, audioCodecToAVName, checkAVAvailable, codecToAVName, createAVNodeVideoPlugin, formatToExtension, formatToMimeType, imageFormatToEncoder };
98
+ export { AVCheckResult, audioCodecToAVName, checkAVAvailable, codecToAVName, createVideoPlugin, formatToExtension, formatToMimeType, imageFormatToEncoder, videoPlugin, videoPluginWithCheck };
99
99
  //# sourceMappingURL=index.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/utils/av-check.ts","../src/utils/format-mappings.ts","../src/video-plugin.ts","../src/video-plugin-layer.ts"],"sourcesContent":[],"mappings":";;;;;;;;KAGY,aAAA;;EAAA,OAAA,CAAA,EAAA,MAAa;EAUH,KAAA,CAAA,EAAA,MAAA;;;;ACKtB;AAWA;AAWa,iBD3BS,gBAAA,CAAA,CCmCrB,EDnCyC,OCmCzC,CDnCiD,aCmCjD,CAAA;;;;;;AD7CW,cCeC,gBDfY,ECeM,MDfN,CCea,oBDfb,CAAA,QAAA,CAAA,EAAA,MAAA,CAAA;AAUzB;;;cCgBa,mBAAmB,OAAO;AAXvC;AAWA;AAWA;AACc,cADD,aACC,EADc,MACd,CAAZ,WAAY,CAAA,oBAAA,CAAA,OAAA,CAAA,CAAA,EACZ,cADY,CAAA;;;;AADoB,cAarB,kBAbqB,EAaD,MAbC,CAchC,WAdgC,CAcpB,oBAdoB,CAAA,YAAA,CAAA,CAAA,EAehC,cAfgC,CAAA;AAalC;;;AAEE,cAWW,oBAXX,EAWiC,MAXjC,CAAA,MAAA,EAWgD,cAXhD,CAAA;;;;;;iBCfc,uBAAA,CAAA,GAA2B;;;;;;AFrC3C;AAUA;;;;ACKA;AAWA;AAWA;;;;;;AAaA;;;;;;AAaA;cEtCa,mBAAiB,KAAA,CAAA,MAAA;;;ADY9B;;;;ACZA;AA4BA;;;;;;;;;;;;;;;;cAAa,4BAA0B,KAAA,CAAA,MAAA"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/utils/av-check.ts","../src/utils/format-mappings.ts","../src/video-plugin.ts","../src/video-plugin-layer.ts"],"sourcesContent":[],"mappings":";;;;;;;;KAGY,aAAA;;EAAA,OAAA,CAAA,EAAA,MAAa;EAUH,KAAA,CAAA,EAAA,MAAA;;;;ACKtB;AAWA;AAWa,iBD3BS,gBAAA,CAAA,CCmCrB,EDnCyC,OCmCzC,CDnCiD,aCmCjD,CAAA;;;;;;AD7CW,cCeC,gBDfY,ECeM,MDfN,CCea,oBDfb,CAAA,QAAA,CAAA,EAAA,MAAA,CAAA;AAUzB;;;cCgBa,mBAAmB,OAAO;AAXvC;AAWA;AAWA;AACc,cADD,aACC,EADc,MACd,CAAZ,WAAY,CAAA,oBAAA,CAAA,OAAA,CAAA,CAAA,EACZ,cADY,CAAA;;;;AADoB,cAarB,kBAbqB,EAaD,MAbC,CAchC,WAdgC,CAcpB,oBAdoB,CAAA,YAAA,CAAA,CAAA,EAehC,cAfgC,CAAA;AAalC;;;AAEE,cAWW,oBAXX,EAWiC,MAXjC,CAAA,MAAA,EAWgD,cAXhD,CAAA;;;;;;iBCfc,iBAAA,CAAA,GAAqB;;;;;;AFrCrC;AAUA;;;;ACKA;AAWA;AAWA;;;;;;AAaA;;;;;;AAaA;cEtCa,mBAAW,KAAA,CAAA,MAAA;;;ADYxB;;;;ACZA;AA0BA;;;;;;;;;;;;;;;;cAAa,4BAAoB,KAAA,CAAA,MAAA"}
package/dist/index.d.mts CHANGED
@@ -43,7 +43,7 @@ declare const imageFormatToEncoder: Record<string, FFEncoderCodec>;
43
43
  /**
44
44
  * Creates a node-av based video processing plugin
45
45
  */
46
- declare function createAVNodeVideoPlugin(): VideoPluginShape;
46
+ declare function createVideoPlugin(): VideoPluginShape;
47
47
  //#endregion
48
48
  //#region src/video-plugin-layer.d.ts
49
49
  /**
@@ -69,7 +69,7 @@ declare function createAVNodeVideoPlugin(): VideoPluginShape;
69
69
  * );
70
70
  * ```
71
71
  */
72
- declare const AVNodeVideoPlugin: Layer.Layer<VideoPlugin, never, never>;
72
+ declare const videoPlugin: () => Layer.Layer<VideoPlugin, never, never>;
73
73
  /**
74
74
  * Effect Layer for the node-av video plugin with availability check
75
75
  *
@@ -93,7 +93,7 @@ declare const AVNodeVideoPlugin: Layer.Layer<VideoPlugin, never, never>;
93
93
  * );
94
94
  * ```
95
95
  */
96
- declare const AVNodeVideoPluginWithCheck: Layer.Layer<VideoPlugin, never, never>;
96
+ declare const videoPluginWithCheck: () => Layer.Layer<VideoPlugin, never, never>;
97
97
  //#endregion
98
- export { AVCheckResult, AVNodeVideoPlugin, AVNodeVideoPluginWithCheck, audioCodecToAVName, checkAVAvailable, codecToAVName, createAVNodeVideoPlugin, formatToExtension, formatToMimeType, imageFormatToEncoder };
98
+ export { AVCheckResult, audioCodecToAVName, checkAVAvailable, codecToAVName, createVideoPlugin, formatToExtension, formatToMimeType, imageFormatToEncoder, videoPlugin, videoPluginWithCheck };
99
99
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/utils/av-check.ts","../src/utils/format-mappings.ts","../src/video-plugin.ts","../src/video-plugin-layer.ts"],"sourcesContent":[],"mappings":";;;;;;;;KAGY,aAAA;;EAAA,OAAA,CAAA,EAAA,MAAa;EAUH,KAAA,CAAA,EAAA,MAAA;;;;ACKtB;AAWA;AAWa,iBD3BS,gBAAA,CAAA,CCmCrB,EDnCyC,OCmCzC,CDnCiD,aCmCjD,CAAA;;;;;;AD7CW,cCeC,gBDfY,ECeM,MDfN,CCea,oBDfb,CAAA,QAAA,CAAA,EAAA,MAAA,CAAA;AAUzB;;;cCgBa,mBAAmB,OAAO;AAXvC;AAWA;AAWA;AACc,cADD,aACC,EADc,MACd,CAAZ,WAAY,CAAA,oBAAA,CAAA,OAAA,CAAA,CAAA,EACZ,cADY,CAAA;;;;AADoB,cAarB,kBAbqB,EAaD,MAbC,CAchC,WAdgC,CAcpB,oBAdoB,CAAA,YAAA,CAAA,CAAA,EAehC,cAfgC,CAAA;AAalC;;;AAEE,cAWW,oBAXX,EAWiC,MAXjC,CAAA,MAAA,EAWgD,cAXhD,CAAA;;;;;;iBCfc,uBAAA,CAAA,GAA2B;;;;;;AFrC3C;AAUA;;;;ACKA;AAWA;AAWA;;;;;;AAaA;;;;;;AAaA;cEtCa,mBAAiB,KAAA,CAAA,MAAA;;;ADY9B;;;;ACZA;AA4BA;;;;;;;;;;;;;;;;cAAa,4BAA0B,KAAA,CAAA,MAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/utils/av-check.ts","../src/utils/format-mappings.ts","../src/video-plugin.ts","../src/video-plugin-layer.ts"],"sourcesContent":[],"mappings":";;;;;;;;KAGY,aAAA;;EAAA,OAAA,CAAA,EAAA,MAAa;EAUH,KAAA,CAAA,EAAA,MAAA;;;;ACKtB;AAWA;AAWa,iBD3BS,gBAAA,CAAA,CCmCrB,EDnCyC,OCmCzC,CDnCiD,aCmCjD,CAAA;;;;;;AD7CW,cCeC,gBDfY,ECeM,MDfN,CCea,oBDfb,CAAA,QAAA,CAAA,EAAA,MAAA,CAAA;AAUzB;;;cCgBa,mBAAmB,OAAO;AAXvC;AAWA;AAWA;AACc,cADD,aACC,EADc,MACd,CAAZ,WAAY,CAAA,oBAAA,CAAA,OAAA,CAAA,CAAA,EACZ,cADY,CAAA;;;;AADoB,cAarB,kBAbqB,EAaD,MAbC,CAchC,WAdgC,CAcpB,oBAdoB,CAAA,YAAA,CAAA,CAAA,EAehC,cAfgC,CAAA;AAalC;;;AAEE,cAWW,oBAXX,EAWiC,MAXjC,CAAA,MAAA,EAWgD,cAXhD,CAAA;;;;;;iBCfc,iBAAA,CAAA,GAAqB;;;;;;AFrCrC;AAUA;;;;ACKA;AAWA;AAWA;;;;;;AAaA;;;;;;AAaA;cEtCa,mBAAW,KAAA,CAAA,MAAA;;;ADYxB;;;;ACZA;AA0BA;;;;;;;;;;;;;;;;cAAa,4BAAoB,KAAA,CAAA,MAAA"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import{FF_ENCODER_AAC as e,FF_ENCODER_LIBAOM_AV1 as t,FF_ENCODER_LIBMP3LAME as n,FF_ENCODER_LIBOPUS as r,FF_ENCODER_LIBVORBIS as i,FF_ENCODER_LIBVPX_VP9 as a,FF_ENCODER_LIBX264 as o,FF_ENCODER_LIBX265 as s,FF_ENCODER_MJPEG as c,FF_ENCODER_PNG as l}from"node-av/constants";import{UploadistaError as u}from"@uploadista/core/errors";import{withOperationSpan as d}from"@uploadista/observability";import{Effect as f,Layer as p,Stream as m}from"effect";import{Decoder as h,Demuxer as g,Encoder as _,Muxer as v}from"node-av/api";import{VideoPlugin as y}from"@uploadista/core/flow";async function b(){try{return await import(`node-av`),{available:!0,version:`4.x`}}catch(e){return{available:!1,error:e instanceof Error?e.message:String(e)}}}const x={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},S={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`},C={h264:o,h265:s,vp9:a,av1:t},w={aac:e,mp3:n,opus:r,vorbis:i},T={jpeg:c,mjpeg:c,png:l};function E(){let e=[],t=0n;return{callbacks:{write:n=>(e.push(n),t+=BigInt(n.length),n.length),seek:(e,n)=>{switch(n){case 0:t=e;break;case 1:t+=e;break;case 2:t=e;break}return t}},getOutput:()=>{let t=e.reduce((e,t)=>e+t.length,0),n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return n}}}function D(){let e=[],t=!1,n=null,r=0n;return{callbacks:{write:t=>{let i=new Uint8Array(t);if(e.push(i),r+=BigInt(t.length),n){let e=n;n=null,e()}return t.length},seek:(e,t)=>{switch(t){case 0:r=e;break;case 1:r+=e;break;case 2:r=e;break}return r}},stream:m.async(r=>((async()=>{for(;;){for(;e.length>0;){let t=e.shift();t&&r.single(t)}if(t&&e.length===0){r.end();return}await new Promise(r=>{n=r,(e.length>0||t)&&(n=null,r())})}})().catch(e=>{r.fail(u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming output failed: ${e instanceof Error?e.message:String(e)}`,cause:e}))}),f.void)),finalize:()=>{if(t=!0,n){let e=n;n=null,e()}}}}function O(e){return f.gen(function*(){let t=[];yield*m.runForEach(e,e=>f.sync(()=>{t.push(e)}));let n=t.reduce((e,t)=>e+t.byteLength,0),r=new Uint8Array(n),i=0;for(let e of t)r.set(e,i),i+=e.byteLength;return r})}function k(e){if(!e)return!1;let t=e.toLowerCase();return t===`video/mp2t`||t===`video/mpeg-ts`||t===`video/mpegts`||t===`application/x-mpegts`}function A(e){return typeof e==`object`&&!!e&&`pipe`in e&&typeof e.pipe==`function`}function j(){return{describe:e=>f.tryPromise({try:async()=>{let t=Buffer.from(e);await using n=await g.open(t);let r=n.video(),i=n.audio();if(!r)throw Error(`No video stream found`);let a=r.codecpar,o=0;if(r.rFrameRate){let{num:e,den:t}=r.rFrameRate;o=t?e/t:e}let s=`unknown`;if(r.sampleAspectRatio){let{num:e,den:t}=r.sampleAspectRatio;s=`${e}:${t}`}return{duration:n.duration||0,width:a.width||0,height:a.height||0,codec:String(a.codecId)||`unknown`,format:n.formatName||`unknown`,bitrate:n.bitRate||0,frameRate:o,aspectRatio:s,hasAudio:!!i,audioCodec:i?.codecpar.codecId?String(i.codecpar.codecId):void 0,audioBitrate:i?.codecpar.bitRate?Number(i.codecpar.bitRate):void 0,size:e.byteLength}},catch:e=>u.fromCode(`VIDEO_METADATA_EXTRACTION_FAILED`,{body:`Failed to extract video metadata: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe(d(`video`,`describe`,{"video.input_size":e.byteLength})),transcode:(e,t)=>f.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=E();await using a=await g.open(n),o=await v.open(r,{format:t.format});let s=a.video();if(!s)throw Error(`No video stream found`);using c=await h.create(s);let l=t.codec?C[t.codec]:C.h264;using u=await _.create(l,{...t.videoBitrate&&{bitrate:t.videoBitrate}});let d=o.addStream(u);for await(using e of c.frames(a.packets(s.index))){if(!e)continue;await u.encode(e);let t=await u.receive();for(;t;)await o.writePacket(t,d),t.free(),t=await u.receive()}await u.flush();let f=await u.receive();for(;f;)await o.writePacket(f,d),f.free(),f=await u.receive();let p=a.audio();if(p){using e=await h.create(p);let n=t.audioCodec?w[t.audioCodec]:w.aac;using r=await _.create(n,{...t.audioBitrate&&{bitrate:t.audioBitrate}});let i=o.addStream(r);for await(using t of e.frames(a.packets(p.index))){if(!t)continue;await r.encode(t);let e=await r.receive();for(;e;)await o.writePacket(e,i),e.free(),e=await r.receive()}await r.flush();let s=await r.receive();for(;s;)await o.writePacket(s,i),s.free(),s=await r.receive()}return i()},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Transcode failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe(d(`video`,`transcode`,{"video.format":t.format,"video.codec":t.codec,"video.video_bitrate":t.videoBitrate,"video.audio_codec":t.audioCodec,"video.audio_bitrate":t.audioBitrate,"video.input_size":e.byteLength})),resize:(e,t)=>f.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=E();await using a=await g.open(n),o=await v.open(r,{format:`mp4`});let s=a.video();if(!s)throw Error(`No video stream found`);using c=await h.create(s);if(!t.width&&!t.height)throw Error(`Either width or height must be specified`);using l=await _.create(C.h264);let u=o.addStream(l);for await(using e of c.frames(a.packets(s.index))){if(!e)continue;await l.encode(e);let t=await l.receive();for(;t;)await o.writePacket(t,u),t.free(),t=await l.receive()}await l.flush();let d=await l.receive();for(;d;)await o.writePacket(d,u),d.free(),d=await l.receive();let f=a.audio();if(f){using e=await h.create(f),t=await _.create(w.aac);let n=o.addStream(t);for await(using r of e.frames(a.packets(f.index))){if(!r)continue;await t.encode(r);let e=await t.receive();for(;e;)await o.writePacket(e,n),e.free(),e=await t.receive()}await t.flush();let r=await t.receive();for(;r;)await o.writePacket(r,n),r.free(),r=await t.receive()}return i()},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Resize failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe(d(`video`,`resize`,{"video.width":t.width,"video.height":t.height,"video.input_size":e.byteLength})),trim:(e,t)=>f.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=E();await using a=await g.open(n),o=await v.open(r,{format:`mp4`});let s=a.video();if(!s)throw Error(`No video stream found`);let c;c=t.duration===void 0?t.endTime===void 0?a.duration||1/0:t.endTime:t.startTime+t.duration;using l=await h.create(s),u=await _.create(C.h264);let d=o.addStream(u);for await(using e of l.frames(a.packets(s.index))){if(!e)continue;let n=e.pts||0n,r=s.timeBase?s.timeBase.num/s.timeBase.den:1,i=Number(n)*r;if(i>=t.startTime&&i<c){await u.encode(e);let t=await u.receive();for(;t;)await o.writePacket(t,d),t.free(),t=await u.receive()}if(i>=c)break}await u.flush();let f=await u.receive();for(;f;)await o.writePacket(f,d),f.free(),f=await u.receive();let p=a.audio();if(p){using e=await h.create(p),n=await _.create(w.aac);let r=o.addStream(n);for await(using i of e.frames(a.packets(p.index))){if(!i)continue;let e=i.pts||0n,a=p.timeBase?p.timeBase.num/p.timeBase.den:1,s=Number(e)*a;if(s>=t.startTime&&s<c){await n.encode(i);let e=await n.receive();for(;e;)await o.writePacket(e,r),e.free(),e=await n.receive()}if(s>=c)break}await n.flush();let i=await n.receive();for(;i;)await o.writePacket(i,r),i.free(),i=await n.receive()}return i()},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Trim failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe(d(`video`,`trim`,{"video.start_time":t.startTime,"video.end_time":t.endTime,"video.duration":t.duration,"video.input_size":e.byteLength})),extractFrame:(e,t)=>f.tryPromise({try:async()=>{let n=Buffer.from(e),r=t.format||`jpeg`;await using i=await g.open(n);let a=i.video();if(!a)throw Error(`No video stream found`);using o=await h.create(a);let s=null,c=t.timestamp;for await(using e of o.frames(i.packets(a.index))){if(!e)continue;let t=e.pts||0n,n=a.timeBase?a.timeBase.num/a.timeBase.den:1;if(Number(t)*n>=c){let t=T[r]||T.jpeg;using n=await _.create(t);await n.encode(e);let i=await n.receive();if(i?.data){s=new Uint8Array(i.data),i.free();break}}}if(!s)throw Error(`No frame found at timestamp ${c}`);return s},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Frame extraction failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe(d(`video`,`extract-frame`,{"video.timestamp":t.timestamp,"video.format":t.format??`jpeg`,"video.input_size":e.byteLength})),supportsStreaming:!0,transcodeStream:(e,t,n)=>f.gen(function*(){let n=A(e)?yield*O(e):e,{callbacks:r,stream:i,finalize:a}=D(),o=f.tryPromise({try:async()=>{let e=Buffer.from(n);await using i=await g.open(e),a=await v.open(r,{format:t.format});let o=i.video();if(!o)throw Error(`No video stream found`);using s=await h.create(o);let c=t.codec?C[t.codec]:C.h264;using l=await _.create(c,{...t.videoBitrate&&{bitrate:t.videoBitrate}});let u=a.addStream(l);for await(using e of s.frames(i.packets(o.index))){if(!e)continue;await l.encode(e);let t=await l.receive();for(;t;)await a.writePacket(t,u),t.free(),t=await l.receive()}await l.flush();let d=await l.receive();for(;d;)await a.writePacket(d,u),d.free(),d=await l.receive();let f=i.audio();if(f){using e=await h.create(f);let n=t.audioCodec?w[t.audioCodec]:w.aac;using r=await _.create(n,{...t.audioBitrate&&{bitrate:t.audioBitrate}});let o=a.addStream(r);for await(using t of e.frames(i.packets(f.index))){if(!t)continue;await r.encode(t);let e=await r.receive();for(;e;)await a.writePacket(e,o),e.free(),e=await r.receive()}await r.flush();let s=await r.receive();for(;s;)await a.writePacket(s,o),s.free(),s=await r.receive()}},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming transcode failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*f.fork(f.tap(o,()=>f.sync(a))),i}).pipe(d(`video`,`transcode-stream`,{"video.format":t.format,"video.codec":t.codec,"video.streaming_input":k(n?.inputFormat)})),resizeStream:(e,t,n)=>f.gen(function*(){let n=A(e)?yield*O(e):e,{callbacks:r,stream:i,finalize:a}=D(),o=f.tryPromise({try:async()=>{let e=Buffer.from(n);await using i=await g.open(e),a=await v.open(r,{format:`mp4`});let o=i.video();if(!o)throw Error(`No video stream found`);if(!t.width&&!t.height)throw Error(`Either width or height must be specified`);using s=await h.create(o),c=await _.create(C.h264);let l=a.addStream(c);for await(using e of s.frames(i.packets(o.index))){if(!e)continue;await c.encode(e);let t=await c.receive();for(;t;)await a.writePacket(t,l),t.free(),t=await c.receive()}await c.flush();let u=await c.receive();for(;u;)await a.writePacket(u,l),u.free(),u=await c.receive();let d=i.audio();if(d){using e=await h.create(d),t=await _.create(w.aac);let n=a.addStream(t);for await(using r of e.frames(i.packets(d.index))){if(!r)continue;await t.encode(r);let e=await t.receive();for(;e;)await a.writePacket(e,n),e.free(),e=await t.receive()}await t.flush();let r=await t.receive();for(;r;)await a.writePacket(r,n),r.free(),r=await t.receive()}},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming resize failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*f.fork(f.tap(o,()=>f.sync(a))),i}).pipe(d(`video`,`resize-stream`,{"video.width":t.width,"video.height":t.height,"video.streaming_input":k(n?.inputFormat)})),trimStream:(e,t,n)=>f.gen(function*(){let n=A(e)?yield*O(e):e,{callbacks:r,stream:i,finalize:a}=D(),o=f.tryPromise({try:async()=>{let e=Buffer.from(n);await using i=await g.open(e),a=await v.open(r,{format:`mp4`});let o=i.video();if(!o)throw Error(`No video stream found`);let s;s=t.duration===void 0?t.endTime===void 0?i.duration||1/0:t.endTime:t.startTime+t.duration;using c=await h.create(o),l=await _.create(C.h264);let u=a.addStream(l);for await(using e of c.frames(i.packets(o.index))){if(!e)continue;let n=e.pts||0n,r=o.timeBase?o.timeBase.num/o.timeBase.den:1,i=Number(n)*r;if(i>=t.startTime&&i<s){await l.encode(e);let t=await l.receive();for(;t;)await a.writePacket(t,u),t.free(),t=await l.receive()}if(i>=s)break}await l.flush();let d=await l.receive();for(;d;)await a.writePacket(d,u),d.free(),d=await l.receive();let f=i.audio();if(f){using e=await h.create(f),n=await _.create(w.aac);let r=a.addStream(n);for await(using o of e.frames(i.packets(f.index))){if(!o)continue;let e=o.pts||0n,i=f.timeBase?f.timeBase.num/f.timeBase.den:1,c=Number(e)*i;if(c>=t.startTime&&c<s){await n.encode(o);let e=await n.receive();for(;e;)await a.writePacket(e,r),e.free(),e=await n.receive()}if(c>=s)break}await n.flush();let o=await n.receive();for(;o;)await a.writePacket(o,r),o.free(),o=await n.receive()}},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming trim failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*f.fork(f.tap(o,()=>f.sync(a))),i}).pipe(d(`video`,`trim-stream`,{"video.start_time":t.startTime,"video.end_time":t.endTime,"video.duration":t.duration,"video.streaming_input":k(n?.inputFormat)}))}}const M=p.succeed(y,j()),N=p.effectDiscard(f.gen(function*(){let e=yield*f.promise(()=>b());e.available?console.log(`✓ node-av ${e.version} detected`):console.warn(`⚠️ node-av is not installed or not available.`,`
1
+ import{FF_ENCODER_AAC as e,FF_ENCODER_LIBAOM_AV1 as t,FF_ENCODER_LIBMP3LAME as n,FF_ENCODER_LIBOPUS as r,FF_ENCODER_LIBVORBIS as i,FF_ENCODER_LIBVPX_VP9 as a,FF_ENCODER_LIBX264 as o,FF_ENCODER_LIBX265 as s,FF_ENCODER_MJPEG as c,FF_ENCODER_PNG as l}from"node-av/constants";import{UploadistaError as u}from"@uploadista/core/errors";import{withOperationSpan as d}from"@uploadista/observability";import{Effect as f,Layer as p,Stream as m}from"effect";import{Decoder as h,Demuxer as g,Encoder as _,Muxer as v}from"node-av/api";import{VideoPlugin as y}from"@uploadista/core/flow";async function b(){try{return await import(`node-av`),{available:!0,version:`4.x`}}catch(e){return{available:!1,error:e instanceof Error?e.message:String(e)}}}const x={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},S={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`},C={h264:o,h265:s,vp9:a,av1:t},w={aac:e,mp3:n,opus:r,vorbis:i},T={jpeg:c,mjpeg:c,png:l};function E(){let e=[],t=0n;return{callbacks:{write:n=>(e.push(n),t+=BigInt(n.length),n.length),seek:(e,n)=>{switch(n){case 0:t=e;break;case 1:t+=e;break;case 2:t=e;break}return t}},getOutput:()=>{let t=e.reduce((e,t)=>e+t.length,0),n=new Uint8Array(t),r=0;for(let t of e)n.set(t,r),r+=t.length;return n}}}function D(){let e=[],t=!1,n=null,r=0n;return{callbacks:{write:t=>{let i=new Uint8Array(t);if(e.push(i),r+=BigInt(t.length),n){let e=n;n=null,e()}return t.length},seek:(e,t)=>{switch(t){case 0:r=e;break;case 1:r+=e;break;case 2:r=e;break}return r}},stream:m.async(r=>((async()=>{for(;;){for(;e.length>0;){let t=e.shift();t&&r.single(t)}if(t&&e.length===0){r.end();return}await new Promise(r=>{n=r,(e.length>0||t)&&(n=null,r())})}})().catch(e=>{r.fail(u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming output failed: ${e instanceof Error?e.message:String(e)}`,cause:e}))}),f.void)),finalize:()=>{if(t=!0,n){let e=n;n=null,e()}}}}function O(e){return f.gen(function*(){let t=[];yield*m.runForEach(e,e=>f.sync(()=>{t.push(e)}));let n=t.reduce((e,t)=>e+t.byteLength,0),r=new Uint8Array(n),i=0;for(let e of t)r.set(e,i),i+=e.byteLength;return r})}function k(e){if(!e)return!1;let t=e.toLowerCase();return t===`video/mp2t`||t===`video/mpeg-ts`||t===`video/mpegts`||t===`application/x-mpegts`}function A(e){return typeof e==`object`&&!!e&&`pipe`in e&&typeof e.pipe==`function`}function j(){return{describe:e=>f.tryPromise({try:async()=>{let t=Buffer.from(e);await using n=await g.open(t);let r=n.video(),i=n.audio();if(!r)throw Error(`No video stream found`);let a=r.codecpar,o=0;if(r.rFrameRate){let{num:e,den:t}=r.rFrameRate;o=t?e/t:e}let s=`unknown`;if(r.sampleAspectRatio){let{num:e,den:t}=r.sampleAspectRatio;s=`${e}:${t}`}return{duration:n.duration||0,width:a.width||0,height:a.height||0,codec:String(a.codecId)||`unknown`,format:n.formatName||`unknown`,bitrate:n.bitRate||0,frameRate:o,aspectRatio:s,hasAudio:!!i,audioCodec:i?.codecpar.codecId?String(i.codecpar.codecId):void 0,audioBitrate:i?.codecpar.bitRate?Number(i.codecpar.bitRate):void 0,size:e.byteLength}},catch:e=>u.fromCode(`VIDEO_METADATA_EXTRACTION_FAILED`,{body:`Failed to extract video metadata: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe(d(`video`,`describe`,{"video.input_size":e.byteLength})),transcode:(e,t)=>f.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=E();await using a=await g.open(n),o=await v.open(r,{format:t.format});let s=a.video();if(!s)throw Error(`No video stream found`);using c=await h.create(s);let l=t.codec?C[t.codec]:C.h264;using u=await _.create(l,{...t.videoBitrate&&{bitrate:t.videoBitrate}});let d=o.addStream(u);for await(using e of c.frames(a.packets(s.index))){if(!e)continue;await u.encode(e);let t=await u.receive();for(;t;)await o.writePacket(t,d),t.free(),t=await u.receive()}await u.flush();let f=await u.receive();for(;f;)await o.writePacket(f,d),f.free(),f=await u.receive();let p=a.audio();if(p){using e=await h.create(p);let n=t.audioCodec?w[t.audioCodec]:w.aac;using r=await _.create(n,{...t.audioBitrate&&{bitrate:t.audioBitrate}});let i=o.addStream(r);for await(using t of e.frames(a.packets(p.index))){if(!t)continue;await r.encode(t);let e=await r.receive();for(;e;)await o.writePacket(e,i),e.free(),e=await r.receive()}await r.flush();let s=await r.receive();for(;s;)await o.writePacket(s,i),s.free(),s=await r.receive()}return i()},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Transcode failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe(d(`video`,`transcode`,{"video.format":t.format,"video.codec":t.codec,"video.video_bitrate":t.videoBitrate,"video.audio_codec":t.audioCodec,"video.audio_bitrate":t.audioBitrate,"video.input_size":e.byteLength})),resize:(e,t)=>f.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=E();await using a=await g.open(n),o=await v.open(r,{format:`mp4`});let s=a.video();if(!s)throw Error(`No video stream found`);using c=await h.create(s);if(!t.width&&!t.height)throw Error(`Either width or height must be specified`);using l=await _.create(C.h264);let u=o.addStream(l);for await(using e of c.frames(a.packets(s.index))){if(!e)continue;await l.encode(e);let t=await l.receive();for(;t;)await o.writePacket(t,u),t.free(),t=await l.receive()}await l.flush();let d=await l.receive();for(;d;)await o.writePacket(d,u),d.free(),d=await l.receive();let f=a.audio();if(f){using e=await h.create(f),t=await _.create(w.aac);let n=o.addStream(t);for await(using r of e.frames(a.packets(f.index))){if(!r)continue;await t.encode(r);let e=await t.receive();for(;e;)await o.writePacket(e,n),e.free(),e=await t.receive()}await t.flush();let r=await t.receive();for(;r;)await o.writePacket(r,n),r.free(),r=await t.receive()}return i()},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Resize failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe(d(`video`,`resize`,{"video.width":t.width,"video.height":t.height,"video.input_size":e.byteLength})),trim:(e,t)=>f.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=E();await using a=await g.open(n),o=await v.open(r,{format:`mp4`});let s=a.video();if(!s)throw Error(`No video stream found`);let c;c=t.duration===void 0?t.endTime===void 0?a.duration||1/0:t.endTime:t.startTime+t.duration;using l=await h.create(s),u=await _.create(C.h264);let d=o.addStream(u);for await(using e of l.frames(a.packets(s.index))){if(!e)continue;let n=e.pts||0n,r=s.timeBase?s.timeBase.num/s.timeBase.den:1,i=Number(n)*r;if(i>=t.startTime&&i<c){await u.encode(e);let t=await u.receive();for(;t;)await o.writePacket(t,d),t.free(),t=await u.receive()}if(i>=c)break}await u.flush();let f=await u.receive();for(;f;)await o.writePacket(f,d),f.free(),f=await u.receive();let p=a.audio();if(p){using e=await h.create(p),n=await _.create(w.aac);let r=o.addStream(n);for await(using i of e.frames(a.packets(p.index))){if(!i)continue;let e=i.pts||0n,a=p.timeBase?p.timeBase.num/p.timeBase.den:1,s=Number(e)*a;if(s>=t.startTime&&s<c){await n.encode(i);let e=await n.receive();for(;e;)await o.writePacket(e,r),e.free(),e=await n.receive()}if(s>=c)break}await n.flush();let i=await n.receive();for(;i;)await o.writePacket(i,r),i.free(),i=await n.receive()}return i()},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Trim failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe(d(`video`,`trim`,{"video.start_time":t.startTime,"video.end_time":t.endTime,"video.duration":t.duration,"video.input_size":e.byteLength})),extractFrame:(e,t)=>f.tryPromise({try:async()=>{let n=Buffer.from(e),r=t.format||`jpeg`;await using i=await g.open(n);let a=i.video();if(!a)throw Error(`No video stream found`);using o=await h.create(a);let s=null,c=t.timestamp;for await(using e of o.frames(i.packets(a.index))){if(!e)continue;let t=e.pts||0n,n=a.timeBase?a.timeBase.num/a.timeBase.den:1;if(Number(t)*n>=c){let t=T[r]||T.jpeg;using n=await _.create(t);await n.encode(e);let i=await n.receive();if(i?.data){s=new Uint8Array(i.data),i.free();break}}}if(!s)throw Error(`No frame found at timestamp ${c}`);return s},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Frame extraction failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}).pipe(d(`video`,`extract-frame`,{"video.timestamp":t.timestamp,"video.format":t.format??`jpeg`,"video.input_size":e.byteLength})),supportsStreaming:!0,transcodeStream:(e,t,n)=>f.gen(function*(){let n=A(e)?yield*O(e):e,{callbacks:r,stream:i,finalize:a}=D(),o=f.tryPromise({try:async()=>{let e=Buffer.from(n);await using i=await g.open(e),a=await v.open(r,{format:t.format});let o=i.video();if(!o)throw Error(`No video stream found`);using s=await h.create(o);let c=t.codec?C[t.codec]:C.h264;using l=await _.create(c,{...t.videoBitrate&&{bitrate:t.videoBitrate}});let u=a.addStream(l);for await(using e of s.frames(i.packets(o.index))){if(!e)continue;await l.encode(e);let t=await l.receive();for(;t;)await a.writePacket(t,u),t.free(),t=await l.receive()}await l.flush();let d=await l.receive();for(;d;)await a.writePacket(d,u),d.free(),d=await l.receive();let f=i.audio();if(f){using e=await h.create(f);let n=t.audioCodec?w[t.audioCodec]:w.aac;using r=await _.create(n,{...t.audioBitrate&&{bitrate:t.audioBitrate}});let o=a.addStream(r);for await(using t of e.frames(i.packets(f.index))){if(!t)continue;await r.encode(t);let e=await r.receive();for(;e;)await a.writePacket(e,o),e.free(),e=await r.receive()}await r.flush();let s=await r.receive();for(;s;)await a.writePacket(s,o),s.free(),s=await r.receive()}},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming transcode failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*f.fork(f.tap(o,()=>f.sync(a))),i}).pipe(d(`video`,`transcode-stream`,{"video.format":t.format,"video.codec":t.codec,"video.streaming_input":k(n?.inputFormat)})),resizeStream:(e,t,n)=>f.gen(function*(){let n=A(e)?yield*O(e):e,{callbacks:r,stream:i,finalize:a}=D(),o=f.tryPromise({try:async()=>{let e=Buffer.from(n);await using i=await g.open(e),a=await v.open(r,{format:`mp4`});let o=i.video();if(!o)throw Error(`No video stream found`);if(!t.width&&!t.height)throw Error(`Either width or height must be specified`);using s=await h.create(o),c=await _.create(C.h264);let l=a.addStream(c);for await(using e of s.frames(i.packets(o.index))){if(!e)continue;await c.encode(e);let t=await c.receive();for(;t;)await a.writePacket(t,l),t.free(),t=await c.receive()}await c.flush();let u=await c.receive();for(;u;)await a.writePacket(u,l),u.free(),u=await c.receive();let d=i.audio();if(d){using e=await h.create(d),t=await _.create(w.aac);let n=a.addStream(t);for await(using r of e.frames(i.packets(d.index))){if(!r)continue;await t.encode(r);let e=await t.receive();for(;e;)await a.writePacket(e,n),e.free(),e=await t.receive()}await t.flush();let r=await t.receive();for(;r;)await a.writePacket(r,n),r.free(),r=await t.receive()}},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming resize failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*f.fork(f.tap(o,()=>f.sync(a))),i}).pipe(d(`video`,`resize-stream`,{"video.width":t.width,"video.height":t.height,"video.streaming_input":k(n?.inputFormat)})),trimStream:(e,t,n)=>f.gen(function*(){let n=A(e)?yield*O(e):e,{callbacks:r,stream:i,finalize:a}=D(),o=f.tryPromise({try:async()=>{let e=Buffer.from(n);await using i=await g.open(e),a=await v.open(r,{format:`mp4`});let o=i.video();if(!o)throw Error(`No video stream found`);let s;s=t.duration===void 0?t.endTime===void 0?i.duration||1/0:t.endTime:t.startTime+t.duration;using c=await h.create(o),l=await _.create(C.h264);let u=a.addStream(l);for await(using e of c.frames(i.packets(o.index))){if(!e)continue;let n=e.pts||0n,r=o.timeBase?o.timeBase.num/o.timeBase.den:1,i=Number(n)*r;if(i>=t.startTime&&i<s){await l.encode(e);let t=await l.receive();for(;t;)await a.writePacket(t,u),t.free(),t=await l.receive()}if(i>=s)break}await l.flush();let d=await l.receive();for(;d;)await a.writePacket(d,u),d.free(),d=await l.receive();let f=i.audio();if(f){using e=await h.create(f),n=await _.create(w.aac);let r=a.addStream(n);for await(using o of e.frames(i.packets(f.index))){if(!o)continue;let e=o.pts||0n,i=f.timeBase?f.timeBase.num/f.timeBase.den:1,c=Number(e)*i;if(c>=t.startTime&&c<s){await n.encode(o);let e=await n.receive();for(;e;)await a.writePacket(e,r),e.free(),e=await n.receive()}if(c>=s)break}await n.flush();let o=await n.receive();for(;o;)await a.writePacket(o,r),o.free(),o=await n.receive()}},catch:e=>u.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Streaming trim failed: ${e instanceof Error?e.message:String(e)}`,cause:e})});return yield*f.fork(f.tap(o,()=>f.sync(a))),i}).pipe(d(`video`,`trim-stream`,{"video.start_time":t.startTime,"video.end_time":t.endTime,"video.duration":t.duration,"video.streaming_input":k(n?.inputFormat)}))}}const M=()=>p.succeed(y,j()),N=()=>p.effectDiscard(f.gen(function*(){let e=yield*f.promise(()=>b());e.available?console.log(`✓ node-av ${e.version} detected`):console.warn(`⚠️ node-av is not installed or not available.`,`
2
2
  Video processing operations will fail.`,`
3
- Install node-av: npm install node-av`)})).pipe(p.provideMerge(M));export{M as AVNodeVideoPlugin,N as AVNodeVideoPluginWithCheck,w as audioCodecToAVName,b as checkAVAvailable,C as codecToAVName,j as createAVNodeVideoPlugin,S as formatToExtension,x as formatToMimeType,T as imageFormatToEncoder};
3
+ Install node-av: npm install node-av`)})).pipe(p.provideMerge(M()));export{w as audioCodecToAVName,b as checkAVAvailable,C as codecToAVName,j as createVideoPlugin,S as formatToExtension,x as formatToMimeType,T as imageFormatToEncoder,M as videoPlugin,N as videoPluginWithCheck};
4
4
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["formatToMimeType: Record<TranscodeVideoParams[\"format\"], string>","formatToExtension: Record<TranscodeVideoParams[\"format\"], string>","codecToAVName: Record<\n NonNullable<TranscodeVideoParams[\"codec\"]>,\n FFEncoderCodec\n>","audioCodecToAVName: Record<\n NonNullable<TranscodeVideoParams[\"audioCodec\"]>,\n FFEncoderCodec\n>","imageFormatToEncoder: Record<string, FFEncoderCodec>","chunks: Buffer[]","callbacks: IOOutputCallbacks","chunkQueue: Uint8Array[]","resolveWaiting: (() => void) | null","callbacks: IOOutputCallbacks","chunks: Uint8Array[]","endTime: number","frameData: Uint8Array | null","inputBuffer: Uint8Array"],"sources":["../src/utils/av-check.ts","../src/utils/format-mappings.ts","../src/utils/memory-io.ts","../src/utils/streaming-io.ts","../src/video-plugin.ts","../src/video-plugin-layer.ts"],"sourcesContent":["/**\n * Result of node-av availability check\n */\nexport type AVCheckResult = {\n available: boolean;\n version?: string;\n error?: string;\n};\n\n/**\n * Checks if node-av is available and can access FFmpeg binaries\n * @returns Promise with availability status and version info\n */\nexport async function checkAVAvailable(): Promise<AVCheckResult> {\n try {\n // Try to import node-av to verify it's available\n await import(\"node-av\");\n\n // node-av includes FFmpeg binaries, so if import succeeds, it's available\n return {\n available: true,\n version: \"4.x\", // node-av version is in package.json\n };\n } catch (error) {\n return {\n available: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n","import type { TranscodeVideoParams } from \"@uploadista/core/flow\";\nimport type { FFEncoderCodec } from \"node-av/constants\";\nimport {\n FF_ENCODER_AAC,\n FF_ENCODER_LIBAOM_AV1,\n FF_ENCODER_LIBMP3LAME,\n FF_ENCODER_LIBOPUS,\n FF_ENCODER_LIBVORBIS,\n FF_ENCODER_LIBVPX_VP9,\n FF_ENCODER_LIBX264,\n FF_ENCODER_LIBX265,\n FF_ENCODER_MJPEG,\n FF_ENCODER_PNG,\n} from \"node-av/constants\";\n\n/**\n * Maps video format to MIME type\n */\nexport const formatToMimeType: Record<TranscodeVideoParams[\"format\"], string> =\n {\n mp4: \"video/mp4\",\n webm: \"video/webm\",\n mov: \"video/quicktime\",\n avi: \"video/x-msvideo\",\n };\n\n/**\n * Maps video format to file extension\n */\nexport const formatToExtension: Record<TranscodeVideoParams[\"format\"], string> =\n {\n mp4: \"mp4\",\n webm: \"webm\",\n mov: \"mov\",\n avi: \"avi\",\n };\n\n/**\n * Maps codec parameter to node-av codec constant\n */\nexport const codecToAVName: Record<\n NonNullable<TranscodeVideoParams[\"codec\"]>,\n FFEncoderCodec\n> = {\n h264: FF_ENCODER_LIBX264,\n h265: FF_ENCODER_LIBX265,\n vp9: FF_ENCODER_LIBVPX_VP9,\n av1: FF_ENCODER_LIBAOM_AV1,\n};\n\n/**\n * Maps audio codec parameter to node-av audio codec constant\n */\nexport const audioCodecToAVName: Record<\n NonNullable<TranscodeVideoParams[\"audioCodec\"]>,\n FFEncoderCodec\n> = {\n aac: FF_ENCODER_AAC,\n mp3: FF_ENCODER_LIBMP3LAME,\n opus: FF_ENCODER_LIBOPUS,\n vorbis: FF_ENCODER_LIBVORBIS,\n};\n\n/**\n * Maps image format to encoder constant\n */\nexport const imageFormatToEncoder: Record<string, FFEncoderCodec> = {\n jpeg: FF_ENCODER_MJPEG,\n mjpeg: FF_ENCODER_MJPEG,\n png: FF_ENCODER_PNG,\n};\n","import type { IOOutputCallbacks } from \"node-av/api\";\n\n/**\n * Creates an in-memory output buffer that implements IOOutputCallbacks\n * @param _format - The output format (e.g., 'mp4', 'webm', 'jpeg') - not used but kept for API clarity\n * @returns IOOutputCallbacks and a function to get the accumulated data\n */\nexport function createMemoryOutput(): {\n callbacks: IOOutputCallbacks;\n getOutput: () => Uint8Array;\n} {\n const chunks: Buffer[] = [];\n let position = 0n;\n\n const callbacks: IOOutputCallbacks = {\n write: (buffer: Buffer): number => {\n chunks.push(buffer);\n position += BigInt(buffer.length);\n return buffer.length;\n },\n seek: (offset: bigint, whence: number): bigint => {\n // For most formats, seeking is optional during muxing\n // We'll implement a basic version that tracks position\n // AVSEEK_SET = 0, AVSEEK_CUR = 1, AVSEEK_END = 2\n switch (whence) {\n case 0: // AVSEEK_SET\n position = offset;\n break;\n case 1: // AVSEEK_CUR\n position += offset;\n break;\n case 2: // AVSEEK_END\n // For end seeking, we'd need to know total size\n // Most streaming formats don't require this\n position = offset;\n break;\n }\n return position;\n },\n };\n\n const getOutput = (): Uint8Array => {\n const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n return result;\n };\n\n return { callbacks, getOutput };\n}\n","import { UploadistaError } from \"@uploadista/core/errors\";\nimport { Effect, Stream } from \"effect\";\nimport type { IOOutputCallbacks } from \"node-av/api\";\n\n/**\n * Creates a streaming output that implements IOOutputCallbacks\n * and provides the output as an Effect Stream for memory-efficient processing.\n *\n * Unlike createMemoryOutput which buffers all data in memory,\n * this utility emits chunks as they are written, allowing them\n * to be streamed directly to storage.\n *\n * @returns IOOutputCallbacks and an Effect Stream of output chunks\n */\nexport function createStreamingOutput(): {\n callbacks: IOOutputCallbacks;\n stream: Stream.Stream<Uint8Array, UploadistaError>;\n finalize: () => void;\n} {\n // Queue to hold chunks until they're consumed\n const chunkQueue: Uint8Array[] = [];\n let isFinalized = false;\n let resolveWaiting: (() => void) | null = null;\n\n // Track position for seek operations\n let position = 0n;\n\n const callbacks: IOOutputCallbacks = {\n write: (buffer: Buffer): number => {\n // Create a copy of the buffer data to avoid issues with buffer reuse\n const chunk = new Uint8Array(buffer);\n chunkQueue.push(chunk);\n position += BigInt(buffer.length);\n\n // If something is waiting for data, notify it\n if (resolveWaiting) {\n const resolve = resolveWaiting;\n resolveWaiting = null;\n resolve();\n }\n\n return buffer.length;\n },\n seek: (offset: bigint, whence: number): bigint => {\n // Handle seeking for container formats\n // AVSEEK_SET = 0, AVSEEK_CUR = 1, AVSEEK_END = 2\n switch (whence) {\n case 0: // AVSEEK_SET\n position = offset;\n break;\n case 1: // AVSEEK_CUR\n position += offset;\n break;\n case 2: // AVSEEK_END\n // For streaming, we can't know the end position\n // Most streaming-compatible formats minimize this\n position = offset;\n break;\n }\n return position;\n },\n };\n\n // Create the output stream\n const stream = Stream.async<Uint8Array, UploadistaError>((emit) => {\n const processQueue = async () => {\n while (true) {\n // Emit any queued chunks\n while (chunkQueue.length > 0) {\n const chunk = chunkQueue.shift();\n if (chunk) {\n emit.single(chunk);\n }\n }\n\n // If finalized and queue is empty, we're done\n if (isFinalized && chunkQueue.length === 0) {\n emit.end();\n return;\n }\n\n // Wait for more data\n await new Promise<void>((resolve) => {\n resolveWaiting = resolve;\n // Check again in case data arrived while we were setting up\n if (chunkQueue.length > 0 || isFinalized) {\n resolveWaiting = null;\n resolve();\n }\n });\n }\n };\n\n // Start processing in the background\n processQueue().catch((error) => {\n emit.fail(\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Streaming output failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n );\n });\n\n return Effect.void;\n });\n\n const finalize = () => {\n isFinalized = true;\n // Wake up any waiting consumers\n if (resolveWaiting) {\n const resolve = resolveWaiting;\n resolveWaiting = null;\n resolve();\n }\n };\n\n return { callbacks, stream, finalize };\n}\n\n/**\n * Collects a stream into a Uint8Array buffer.\n * Used when streaming input is not supported for a format.\n *\n * @param inputStream - The input stream to collect\n * @returns Effect that resolves to the collected buffer\n */\nexport function collectStreamToBuffer(\n inputStream: Stream.Stream<Uint8Array, UploadistaError>,\n): Effect.Effect<Uint8Array, UploadistaError> {\n return Effect.gen(function* () {\n const chunks: Uint8Array[] = [];\n yield* Stream.runForEach(inputStream, (chunk) =>\n Effect.sync(() => {\n chunks.push(chunk);\n }),\n );\n\n const totalLength = chunks.reduce((sum, c) => sum + c.byteLength, 0);\n const buffer = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n buffer.set(chunk, offset);\n offset += chunk.byteLength;\n }\n\n return buffer;\n });\n}\n\n/**\n * Checks if the input is an MPEG-TS format based on the sync byte.\n * MPEG-TS packets start with the sync byte 0x47.\n *\n * @param header - First bytes of the file (at least 1 byte needed)\n * @returns true if the format appears to be MPEG-TS\n */\nexport function isMpegTS(header: Uint8Array): boolean {\n // MPEG-TS sync byte\n return header.length > 0 && header[0] === 0x47;\n}\n\n/**\n * Checks if a MIME type indicates MPEG-TS format.\n *\n * @param mimeType - The MIME type to check\n * @returns true if the MIME type indicates MPEG-TS\n */\nexport function isMpegTSMimeType(mimeType: string | undefined): boolean {\n if (!mimeType) return false;\n const normalizedType = mimeType.toLowerCase();\n return (\n normalizedType === \"video/mp2t\" ||\n normalizedType === \"video/mpeg-ts\" ||\n normalizedType === \"video/mpegts\" ||\n normalizedType === \"application/x-mpegts\"\n );\n}\n","import { UploadistaError } from \"@uploadista/core/errors\";\nimport type {\n DescribeVideoMetadata,\n VideoPluginShape,\n VideoStreamInput,\n VideoStreamOptions,\n} from \"@uploadista/core/flow\";\nimport { withOperationSpan } from \"@uploadista/observability\";\nimport { Effect, type Stream } from \"effect\";\nimport { Decoder, Demuxer, Encoder, Muxer } from \"node-av/api\";\nimport {\n audioCodecToAVName,\n codecToAVName,\n imageFormatToEncoder,\n} from \"./utils/format-mappings\";\nimport { createMemoryOutput } from \"./utils/memory-io\";\nimport {\n collectStreamToBuffer,\n createStreamingOutput,\n isMpegTSMimeType,\n} from \"./utils/streaming-io\";\n\n/**\n * Helper to check if a VideoStreamInput is a Stream vs a Uint8Array.\n * Streams have a pipe property that is a function.\n */\nfunction isInputStream(\n input: VideoStreamInput,\n): input is Stream.Stream<Uint8Array, UploadistaError> {\n return (\n typeof input === \"object\" &&\n input !== null &&\n \"pipe\" in input &&\n typeof (input as { pipe: unknown }).pipe === \"function\"\n );\n}\n\n/**\n * Creates a node-av based video processing plugin\n */\nexport function createAVNodeVideoPlugin(): VideoPluginShape {\n return {\n describe: (input) =>\n Effect.tryPromise({\n try: async () => {\n // Convert Uint8Array to Buffer for node-av\n const buffer = Buffer.from(input);\n await using mediaInput = await Demuxer.open(buffer);\n\n const videoStream = mediaInput.video();\n const audioStream = mediaInput.audio();\n\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n const videoCodecParams = videoStream.codecpar;\n\n // Calculate frame rate from rational number\n let frameRate = 0;\n if (videoStream.rFrameRate) {\n const { num, den } = videoStream.rFrameRate;\n frameRate = den ? num / den : num;\n }\n\n // Get aspect ratio\n let aspectRatio = \"unknown\";\n if (videoStream.sampleAspectRatio) {\n const { num, den } = videoStream.sampleAspectRatio;\n aspectRatio = `${num}:${den}`;\n }\n\n const metadata: DescribeVideoMetadata = {\n duration: mediaInput.duration || 0,\n width: videoCodecParams.width || 0,\n height: videoCodecParams.height || 0,\n codec: String(videoCodecParams.codecId) || \"unknown\",\n format: mediaInput.formatName || \"unknown\",\n bitrate: mediaInput.bitRate || 0,\n frameRate,\n aspectRatio,\n hasAudio: !!audioStream,\n audioCodec: audioStream?.codecpar.codecId\n ? String(audioStream.codecpar.codecId)\n : undefined,\n audioBitrate: audioStream?.codecpar.bitRate\n ? Number(audioStream.codecpar.bitRate)\n : undefined,\n size: input.byteLength,\n };\n\n return metadata;\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_METADATA_EXTRACTION_FAILED\", {\n body: `Failed to extract video metadata: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n }).pipe(\n withOperationSpan(\"video\", \"describe\", {\n \"video.input_size\": input.byteLength,\n }),\n ),\n\n transcode: (input, options) =>\n Effect.tryPromise({\n try: async () => {\n // Convert Uint8Array to Buffer for input\n const inputBuffer = Buffer.from(input);\n\n // Create in-memory output\n const { callbacks, getOutput } = createMemoryOutput();\n\n await using mediaInput = await Demuxer.open(inputBuffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: options.format,\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n\n // Determine encoder codec\n const encoderCodec = options.codec\n ? codecToAVName[options.codec]\n : codecToAVName.h264;\n\n using videoEncoder = await Encoder.create(encoderCodec, {\n ...(options.videoBitrate && { bitrate: options.videoBitrate }),\n });\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n // Process video frames\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n // Flush remaining packets\n await videoEncoder.flush();\n let transcodeVPacket = await videoEncoder.receive();\n while (transcodeVPacket) {\n await mediaOutput.writePacket(transcodeVPacket, videoOutputIndex);\n transcodeVPacket.free();\n transcodeVPacket = await videoEncoder.receive();\n }\n\n // Handle audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n\n const audioEncoderCodec = options.audioCodec\n ? audioCodecToAVName[options.audioCodec]\n : audioCodecToAVName.aac;\n\n using audioEncoder = await Encoder.create(audioEncoderCodec, {\n ...(options.audioBitrate && { bitrate: options.audioBitrate }),\n });\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n // Process audio frames\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n // Flush remaining packets\n await audioEncoder.flush();\n let transcodeAPacket = await audioEncoder.receive();\n while (transcodeAPacket) {\n await mediaOutput.writePacket(transcodeAPacket, audioOutputIndex);\n transcodeAPacket.free();\n transcodeAPacket = await audioEncoder.receive();\n }\n }\n\n // Return accumulated output\n return getOutput();\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Transcode failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n }).pipe(\n withOperationSpan(\"video\", \"transcode\", {\n \"video.format\": options.format,\n \"video.codec\": options.codec,\n \"video.video_bitrate\": options.videoBitrate,\n \"video.audio_codec\": options.audioCodec,\n \"video.audio_bitrate\": options.audioBitrate,\n \"video.input_size\": input.byteLength,\n }),\n ),\n\n resize: (input, options) =>\n Effect.tryPromise({\n try: async () => {\n // Convert Uint8Array to Buffer for input\n const inputBuffer = Buffer.from(input);\n\n // Create in-memory output\n const { callbacks, getOutput } = createMemoryOutput();\n\n await using mediaInput = await Demuxer.open(inputBuffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: \"mp4\",\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n\n // TODO: Implement proper resizing with FilterAPI\n // Currently, resize functionality is limited because node-av's Encoder\n // auto-initializes from the first frame it receives. To implement proper\n // resizing, we would need to:\n // 1. Use FilterAPI.create('scale', { width, height }) to create a scale filter\n // 2. Pass decoded frames through the filter before encoding\n // For now, this function will pass through frames without resizing.\n\n // Validate that resize parameters are provided\n if (!options.width && !options.height) {\n throw new Error(\"Either width or height must be specified\");\n }\n\n using videoEncoder = await Encoder.create(codecToAVName.h264);\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n // Process video frames with resizing\n // Note: For production use, consider using FilterAPI for better quality scaling\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n // TODO: Apply scale filter here for better quality\n // For now, encoder will handle basic resizing\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n // Flush remaining packets\n await videoEncoder.flush();\n let vPacket = await videoEncoder.receive();\n while (vPacket) {\n await mediaOutput.writePacket(vPacket, videoOutputIndex);\n vPacket.free();\n vPacket = await videoEncoder.receive();\n }\n\n // Copy audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n using audioEncoder = await Encoder.create(audioCodecToAVName.aac);\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n // Flush remaining packets\n await audioEncoder.flush();\n let resizeAPacket = await audioEncoder.receive();\n while (resizeAPacket) {\n await mediaOutput.writePacket(resizeAPacket, audioOutputIndex);\n resizeAPacket.free();\n resizeAPacket = await audioEncoder.receive();\n }\n }\n\n // Return accumulated output\n return getOutput();\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Resize failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n }).pipe(\n withOperationSpan(\"video\", \"resize\", {\n \"video.width\": options.width,\n \"video.height\": options.height,\n \"video.input_size\": input.byteLength,\n }),\n ),\n\n trim: (input, options) =>\n Effect.tryPromise({\n try: async () => {\n // Convert Uint8Array to Buffer for input\n const inputBuffer = Buffer.from(input);\n\n // Create in-memory output\n const { callbacks, getOutput } = createMemoryOutput();\n\n await using mediaInput = await Demuxer.open(inputBuffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: \"mp4\",\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n // Calculate end time\n let endTime: number;\n if (options.duration !== undefined) {\n endTime = options.startTime + options.duration;\n } else if (options.endTime !== undefined) {\n endTime = options.endTime;\n } else {\n endTime = mediaInput.duration || Number.POSITIVE_INFINITY;\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n using videoEncoder = await Encoder.create(codecToAVName.h264);\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n // Process video frames within time range\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n // Calculate frame timestamp\n const pts = frame.pts || 0n;\n const timeBase = videoStream.timeBase\n ? videoStream.timeBase.num / videoStream.timeBase.den\n : 1;\n const timestamp = Number(pts) * timeBase;\n\n if (timestamp >= options.startTime && timestamp < endTime) {\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n if (timestamp >= endTime) break;\n }\n\n // Flush remaining packets\n await videoEncoder.flush();\n let trimVPacket = await videoEncoder.receive();\n while (trimVPacket) {\n await mediaOutput.writePacket(trimVPacket, videoOutputIndex);\n trimVPacket.free();\n trimVPacket = await videoEncoder.receive();\n }\n\n // Handle audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n using audioEncoder = await Encoder.create(audioCodecToAVName.aac);\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n const pts = frame.pts || 0n;\n const timeBase = audioStream.timeBase\n ? audioStream.timeBase.num / audioStream.timeBase.den\n : 1;\n const timestamp = Number(pts) * timeBase;\n\n if (timestamp >= options.startTime && timestamp < endTime) {\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n if (timestamp >= endTime) break;\n }\n\n // Flush remaining packets\n await audioEncoder.flush();\n let trimAPacket = await audioEncoder.receive();\n while (trimAPacket) {\n await mediaOutput.writePacket(trimAPacket, audioOutputIndex);\n trimAPacket.free();\n trimAPacket = await audioEncoder.receive();\n }\n }\n\n // Return accumulated output\n return getOutput();\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Trim failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n }).pipe(\n withOperationSpan(\"video\", \"trim\", {\n \"video.start_time\": options.startTime,\n \"video.end_time\": options.endTime,\n \"video.duration\": options.duration,\n \"video.input_size\": input.byteLength,\n }),\n ),\n\n extractFrame: (input, options) =>\n Effect.tryPromise({\n try: async () => {\n // Convert Uint8Array to Buffer for input\n const inputBuffer = Buffer.from(input);\n const format = options.format || \"jpeg\";\n\n await using mediaInput = await Demuxer.open(inputBuffer);\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n using decoder = await Decoder.create(videoStream);\n\n let frameData: Uint8Array | null = null;\n const targetTimestamp = options.timestamp;\n\n for await (using frame of decoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n // Calculate frame timestamp\n const pts = frame.pts || 0n;\n const timeBase = videoStream.timeBase\n ? videoStream.timeBase.num / videoStream.timeBase.den\n : 1;\n const timestamp = Number(pts) * timeBase;\n\n // Look for frame at or after target timestamp\n if (timestamp >= targetTimestamp) {\n // Use an image encoder to save the frame\n const encoderCodec =\n imageFormatToEncoder[format] || imageFormatToEncoder.jpeg;\n using imageEncoder = await Encoder.create(encoderCodec);\n\n // Encode the frame as image\n // The encoder will initialize from the first frame's properties\n await imageEncoder.encode(frame);\n const packet = await imageEncoder.receive();\n if (packet?.data) {\n // Convert Buffer to Uint8Array\n frameData = new Uint8Array(packet.data);\n packet.free();\n break;\n }\n }\n }\n\n if (!frameData) {\n throw new Error(`No frame found at timestamp ${targetTimestamp}`);\n }\n\n return frameData;\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Frame extraction failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n }).pipe(\n withOperationSpan(\"video\", \"extract-frame\", {\n \"video.timestamp\": options.timestamp,\n \"video.format\": options.format ?? \"jpeg\",\n \"video.input_size\": input.byteLength,\n }),\n ),\n\n // Streaming support flag\n supportsStreaming: true,\n\n // Streaming transcode implementation\n transcodeStream: (\n input: VideoStreamInput,\n options,\n streamOptions?: VideoStreamOptions,\n ) =>\n Effect.gen(function* () {\n // Convert stream input to buffer if needed\n // Currently MPEG-TS input streaming is not yet implemented\n const inputBuffer: Uint8Array = isInputStream(input)\n ? yield* collectStreamToBuffer(input)\n : input;\n\n // Create streaming output\n const { callbacks, stream, finalize } = createStreamingOutput();\n\n // Start processing in background and return stream immediately\n const processVideo = Effect.tryPromise({\n try: async () => {\n const buffer = Buffer.from(inputBuffer);\n\n await using mediaInput = await Demuxer.open(buffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: options.format,\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n\n const encoderCodec = options.codec\n ? codecToAVName[options.codec]\n : codecToAVName.h264;\n\n using videoEncoder = await Encoder.create(encoderCodec, {\n ...(options.videoBitrate && { bitrate: options.videoBitrate }),\n });\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n // Process video frames\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n // Flush remaining packets\n await videoEncoder.flush();\n let vPacket = await videoEncoder.receive();\n while (vPacket) {\n await mediaOutput.writePacket(vPacket, videoOutputIndex);\n vPacket.free();\n vPacket = await videoEncoder.receive();\n }\n\n // Handle audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n\n const audioEncoderCodec = options.audioCodec\n ? audioCodecToAVName[options.audioCodec]\n : audioCodecToAVName.aac;\n\n using audioEncoder = await Encoder.create(audioEncoderCodec, {\n ...(options.audioBitrate && { bitrate: options.audioBitrate }),\n });\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n await audioEncoder.flush();\n let aPacket = await audioEncoder.receive();\n while (aPacket) {\n await mediaOutput.writePacket(aPacket, audioOutputIndex);\n aPacket.free();\n aPacket = await audioEncoder.receive();\n }\n }\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Streaming transcode failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n });\n\n // Fork processing and finalize when done\n yield* Effect.fork(\n Effect.tap(processVideo, () => Effect.sync(finalize)),\n );\n\n return stream;\n }).pipe(\n withOperationSpan(\"video\", \"transcode-stream\", {\n \"video.format\": options.format,\n \"video.codec\": options.codec,\n \"video.streaming_input\": isMpegTSMimeType(streamOptions?.inputFormat),\n }),\n ),\n\n // Streaming resize implementation\n resizeStream: (\n input: VideoStreamInput,\n options,\n streamOptions?: VideoStreamOptions,\n ) =>\n Effect.gen(function* () {\n // Convert stream input to buffer if needed\n const inputBuffer: Uint8Array = isInputStream(input)\n ? yield* collectStreamToBuffer(input)\n : input;\n\n // Create streaming output\n const { callbacks, stream, finalize } = createStreamingOutput();\n\n const processVideo = Effect.tryPromise({\n try: async () => {\n const buffer = Buffer.from(inputBuffer);\n\n await using mediaInput = await Demuxer.open(buffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: \"mp4\",\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n if (!options.width && !options.height) {\n throw new Error(\"Either width or height must be specified\");\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n using videoEncoder = await Encoder.create(codecToAVName.h264);\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n await videoEncoder.flush();\n let vPacket = await videoEncoder.receive();\n while (vPacket) {\n await mediaOutput.writePacket(vPacket, videoOutputIndex);\n vPacket.free();\n vPacket = await videoEncoder.receive();\n }\n\n // Copy audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n using audioEncoder = await Encoder.create(audioCodecToAVName.aac);\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n await audioEncoder.flush();\n let aPacket = await audioEncoder.receive();\n while (aPacket) {\n await mediaOutput.writePacket(aPacket, audioOutputIndex);\n aPacket.free();\n aPacket = await audioEncoder.receive();\n }\n }\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Streaming resize failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n });\n\n yield* Effect.fork(\n Effect.tap(processVideo, () => Effect.sync(finalize)),\n );\n\n return stream;\n }).pipe(\n withOperationSpan(\"video\", \"resize-stream\", {\n \"video.width\": options.width,\n \"video.height\": options.height,\n \"video.streaming_input\": isMpegTSMimeType(streamOptions?.inputFormat),\n }),\n ),\n\n // Streaming trim implementation\n trimStream: (\n input: VideoStreamInput,\n options,\n streamOptions?: VideoStreamOptions,\n ) =>\n Effect.gen(function* () {\n // Convert stream input to buffer if needed\n const inputBuffer: Uint8Array = isInputStream(input)\n ? yield* collectStreamToBuffer(input)\n : input;\n\n // Create streaming output\n const { callbacks, stream, finalize } = createStreamingOutput();\n\n const processVideo = Effect.tryPromise({\n try: async () => {\n const buffer = Buffer.from(inputBuffer);\n\n await using mediaInput = await Demuxer.open(buffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: \"mp4\",\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n // Calculate end time\n let endTime: number;\n if (options.duration !== undefined) {\n endTime = options.startTime + options.duration;\n } else if (options.endTime !== undefined) {\n endTime = options.endTime;\n } else {\n endTime = mediaInput.duration || Number.POSITIVE_INFINITY;\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n using videoEncoder = await Encoder.create(codecToAVName.h264);\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n const pts = frame.pts || 0n;\n const timeBase = videoStream.timeBase\n ? videoStream.timeBase.num / videoStream.timeBase.den\n : 1;\n const timestamp = Number(pts) * timeBase;\n\n if (timestamp >= options.startTime && timestamp < endTime) {\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n if (timestamp >= endTime) break;\n }\n\n await videoEncoder.flush();\n let vPacket = await videoEncoder.receive();\n while (vPacket) {\n await mediaOutput.writePacket(vPacket, videoOutputIndex);\n vPacket.free();\n vPacket = await videoEncoder.receive();\n }\n\n // Handle audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n using audioEncoder = await Encoder.create(audioCodecToAVName.aac);\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n const pts = frame.pts || 0n;\n const timeBase = audioStream.timeBase\n ? audioStream.timeBase.num / audioStream.timeBase.den\n : 1;\n const timestamp = Number(pts) * timeBase;\n\n if (timestamp >= options.startTime && timestamp < endTime) {\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n if (timestamp >= endTime) break;\n }\n\n await audioEncoder.flush();\n let aPacket = await audioEncoder.receive();\n while (aPacket) {\n await mediaOutput.writePacket(aPacket, audioOutputIndex);\n aPacket.free();\n aPacket = await audioEncoder.receive();\n }\n }\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Streaming trim failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n });\n\n yield* Effect.fork(\n Effect.tap(processVideo, () => Effect.sync(finalize)),\n );\n\n return stream;\n }).pipe(\n withOperationSpan(\"video\", \"trim-stream\", {\n \"video.start_time\": options.startTime,\n \"video.end_time\": options.endTime,\n \"video.duration\": options.duration,\n \"video.streaming_input\": isMpegTSMimeType(streamOptions?.inputFormat),\n }),\n ),\n };\n}\n","import { VideoPlugin } from \"@uploadista/core/flow\";\nimport { Effect, Layer } from \"effect\";\nimport { checkAVAvailable } from \"./utils/av-check\";\nimport { createAVNodeVideoPlugin } from \"./video-plugin\";\n\n/**\n * Effect Layer for the node-av video plugin\n *\n * This layer provides video processing capabilities using node-av (FFmpeg bindings).\n * Note: node-av includes prebuilt FFmpeg binaries, so no system installation is required.\n *\n * @example\n * ```typescript\n * import { AVNodeVideoPlugin } from \"@uploadista/flow-videos-av-node\";\n * import { Effect } from \"effect\";\n *\n * const program = Effect.gen(function* () {\n * const videoPlugin = yield* VideoPlugin;\n * const metadata = yield* videoPlugin.describe(videoBytes);\n * return metadata;\n * });\n *\n * // Run with node-av plugin layer\n * const result = await Effect.runPromise(\n * program.pipe(Effect.provide(AVNodeVideoPluginLive))\n * );\n * ```\n */\nexport const AVNodeVideoPlugin = Layer.succeed(\n VideoPlugin,\n createAVNodeVideoPlugin(),\n);\n\n/**\n * Effect Layer for the node-av video plugin with availability check\n *\n * This layer checks if node-av is properly installed and logs status information.\n * The plugin will still be created, but operations will fail if node-av is not available.\n *\n * @example\n * ```typescript\n * import { AVNodeVideoPluginWithCheck } from \"@uploadista/flow-videos-av-node\";\n * import { Effect } from \"effect\";\n *\n * const program = Effect.gen(function* () {\n * const videoPlugin = yield* VideoPlugin;\n * const metadata = yield* videoPlugin.describe(videoBytes);\n * return metadata;\n * });\n *\n * // Run with node-av plugin layer (with check)\n * const result = await Effect.runPromise(\n * program.pipe(Effect.provide(AVNodeVideoPluginWithCheck))\n * );\n * ```\n */\nexport const AVNodeVideoPluginWithCheck = Layer.effectDiscard(\n Effect.gen(function* () {\n const result = yield* Effect.promise(() => checkAVAvailable());\n\n if (!result.available) {\n console.warn(\n \"⚠️ node-av is not installed or not available.\",\n \"\\nVideo processing operations will fail.\",\n \"\\nInstall node-av: npm install node-av\",\n );\n } else {\n console.log(`✓ node-av ${result.version} detected`);\n }\n }),\n).pipe(Layer.provideMerge(AVNodeVideoPlugin));\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["formatToMimeType: Record<TranscodeVideoParams[\"format\"], string>","formatToExtension: Record<TranscodeVideoParams[\"format\"], string>","codecToAVName: Record<\n NonNullable<TranscodeVideoParams[\"codec\"]>,\n FFEncoderCodec\n>","audioCodecToAVName: Record<\n NonNullable<TranscodeVideoParams[\"audioCodec\"]>,\n FFEncoderCodec\n>","imageFormatToEncoder: Record<string, FFEncoderCodec>","chunks: Buffer[]","callbacks: IOOutputCallbacks","chunkQueue: Uint8Array[]","resolveWaiting: (() => void) | null","callbacks: IOOutputCallbacks","chunks: Uint8Array[]","endTime: number","frameData: Uint8Array | null","inputBuffer: Uint8Array"],"sources":["../src/utils/av-check.ts","../src/utils/format-mappings.ts","../src/utils/memory-io.ts","../src/utils/streaming-io.ts","../src/video-plugin.ts","../src/video-plugin-layer.ts"],"sourcesContent":["/**\n * Result of node-av availability check\n */\nexport type AVCheckResult = {\n available: boolean;\n version?: string;\n error?: string;\n};\n\n/**\n * Checks if node-av is available and can access FFmpeg binaries\n * @returns Promise with availability status and version info\n */\nexport async function checkAVAvailable(): Promise<AVCheckResult> {\n try {\n // Try to import node-av to verify it's available\n await import(\"node-av\");\n\n // node-av includes FFmpeg binaries, so if import succeeds, it's available\n return {\n available: true,\n version: \"4.x\", // node-av version is in package.json\n };\n } catch (error) {\n return {\n available: false,\n error: error instanceof Error ? error.message : String(error),\n };\n }\n}\n","import type { TranscodeVideoParams } from \"@uploadista/core/flow\";\nimport type { FFEncoderCodec } from \"node-av/constants\";\nimport {\n FF_ENCODER_AAC,\n FF_ENCODER_LIBAOM_AV1,\n FF_ENCODER_LIBMP3LAME,\n FF_ENCODER_LIBOPUS,\n FF_ENCODER_LIBVORBIS,\n FF_ENCODER_LIBVPX_VP9,\n FF_ENCODER_LIBX264,\n FF_ENCODER_LIBX265,\n FF_ENCODER_MJPEG,\n FF_ENCODER_PNG,\n} from \"node-av/constants\";\n\n/**\n * Maps video format to MIME type\n */\nexport const formatToMimeType: Record<TranscodeVideoParams[\"format\"], string> =\n {\n mp4: \"video/mp4\",\n webm: \"video/webm\",\n mov: \"video/quicktime\",\n avi: \"video/x-msvideo\",\n };\n\n/**\n * Maps video format to file extension\n */\nexport const formatToExtension: Record<TranscodeVideoParams[\"format\"], string> =\n {\n mp4: \"mp4\",\n webm: \"webm\",\n mov: \"mov\",\n avi: \"avi\",\n };\n\n/**\n * Maps codec parameter to node-av codec constant\n */\nexport const codecToAVName: Record<\n NonNullable<TranscodeVideoParams[\"codec\"]>,\n FFEncoderCodec\n> = {\n h264: FF_ENCODER_LIBX264,\n h265: FF_ENCODER_LIBX265,\n vp9: FF_ENCODER_LIBVPX_VP9,\n av1: FF_ENCODER_LIBAOM_AV1,\n};\n\n/**\n * Maps audio codec parameter to node-av audio codec constant\n */\nexport const audioCodecToAVName: Record<\n NonNullable<TranscodeVideoParams[\"audioCodec\"]>,\n FFEncoderCodec\n> = {\n aac: FF_ENCODER_AAC,\n mp3: FF_ENCODER_LIBMP3LAME,\n opus: FF_ENCODER_LIBOPUS,\n vorbis: FF_ENCODER_LIBVORBIS,\n};\n\n/**\n * Maps image format to encoder constant\n */\nexport const imageFormatToEncoder: Record<string, FFEncoderCodec> = {\n jpeg: FF_ENCODER_MJPEG,\n mjpeg: FF_ENCODER_MJPEG,\n png: FF_ENCODER_PNG,\n};\n","import type { IOOutputCallbacks } from \"node-av/api\";\n\n/**\n * Creates an in-memory output buffer that implements IOOutputCallbacks\n * @param _format - The output format (e.g., 'mp4', 'webm', 'jpeg') - not used but kept for API clarity\n * @returns IOOutputCallbacks and a function to get the accumulated data\n */\nexport function createMemoryOutput(): {\n callbacks: IOOutputCallbacks;\n getOutput: () => Uint8Array;\n} {\n const chunks: Buffer[] = [];\n let position = 0n;\n\n const callbacks: IOOutputCallbacks = {\n write: (buffer: Buffer): number => {\n chunks.push(buffer);\n position += BigInt(buffer.length);\n return buffer.length;\n },\n seek: (offset: bigint, whence: number): bigint => {\n // For most formats, seeking is optional during muxing\n // We'll implement a basic version that tracks position\n // AVSEEK_SET = 0, AVSEEK_CUR = 1, AVSEEK_END = 2\n switch (whence) {\n case 0: // AVSEEK_SET\n position = offset;\n break;\n case 1: // AVSEEK_CUR\n position += offset;\n break;\n case 2: // AVSEEK_END\n // For end seeking, we'd need to know total size\n // Most streaming formats don't require this\n position = offset;\n break;\n }\n return position;\n },\n };\n\n const getOutput = (): Uint8Array => {\n const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.length;\n }\n return result;\n };\n\n return { callbacks, getOutput };\n}\n","import { UploadistaError } from \"@uploadista/core/errors\";\nimport { Effect, Stream } from \"effect\";\nimport type { IOOutputCallbacks } from \"node-av/api\";\n\n/**\n * Creates a streaming output that implements IOOutputCallbacks\n * and provides the output as an Effect Stream for memory-efficient processing.\n *\n * Unlike createMemoryOutput which buffers all data in memory,\n * this utility emits chunks as they are written, allowing them\n * to be streamed directly to storage.\n *\n * @returns IOOutputCallbacks and an Effect Stream of output chunks\n */\nexport function createStreamingOutput(): {\n callbacks: IOOutputCallbacks;\n stream: Stream.Stream<Uint8Array, UploadistaError>;\n finalize: () => void;\n} {\n // Queue to hold chunks until they're consumed\n const chunkQueue: Uint8Array[] = [];\n let isFinalized = false;\n let resolveWaiting: (() => void) | null = null;\n\n // Track position for seek operations\n let position = 0n;\n\n const callbacks: IOOutputCallbacks = {\n write: (buffer: Buffer): number => {\n // Create a copy of the buffer data to avoid issues with buffer reuse\n const chunk = new Uint8Array(buffer);\n chunkQueue.push(chunk);\n position += BigInt(buffer.length);\n\n // If something is waiting for data, notify it\n if (resolveWaiting) {\n const resolve = resolveWaiting;\n resolveWaiting = null;\n resolve();\n }\n\n return buffer.length;\n },\n seek: (offset: bigint, whence: number): bigint => {\n // Handle seeking for container formats\n // AVSEEK_SET = 0, AVSEEK_CUR = 1, AVSEEK_END = 2\n switch (whence) {\n case 0: // AVSEEK_SET\n position = offset;\n break;\n case 1: // AVSEEK_CUR\n position += offset;\n break;\n case 2: // AVSEEK_END\n // For streaming, we can't know the end position\n // Most streaming-compatible formats minimize this\n position = offset;\n break;\n }\n return position;\n },\n };\n\n // Create the output stream\n const stream = Stream.async<Uint8Array, UploadistaError>((emit) => {\n const processQueue = async () => {\n while (true) {\n // Emit any queued chunks\n while (chunkQueue.length > 0) {\n const chunk = chunkQueue.shift();\n if (chunk) {\n emit.single(chunk);\n }\n }\n\n // If finalized and queue is empty, we're done\n if (isFinalized && chunkQueue.length === 0) {\n emit.end();\n return;\n }\n\n // Wait for more data\n await new Promise<void>((resolve) => {\n resolveWaiting = resolve;\n // Check again in case data arrived while we were setting up\n if (chunkQueue.length > 0 || isFinalized) {\n resolveWaiting = null;\n resolve();\n }\n });\n }\n };\n\n // Start processing in the background\n processQueue().catch((error) => {\n emit.fail(\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Streaming output failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n );\n });\n\n return Effect.void;\n });\n\n const finalize = () => {\n isFinalized = true;\n // Wake up any waiting consumers\n if (resolveWaiting) {\n const resolve = resolveWaiting;\n resolveWaiting = null;\n resolve();\n }\n };\n\n return { callbacks, stream, finalize };\n}\n\n/**\n * Collects a stream into a Uint8Array buffer.\n * Used when streaming input is not supported for a format.\n *\n * @param inputStream - The input stream to collect\n * @returns Effect that resolves to the collected buffer\n */\nexport function collectStreamToBuffer(\n inputStream: Stream.Stream<Uint8Array, UploadistaError>,\n): Effect.Effect<Uint8Array, UploadistaError> {\n return Effect.gen(function* () {\n const chunks: Uint8Array[] = [];\n yield* Stream.runForEach(inputStream, (chunk) =>\n Effect.sync(() => {\n chunks.push(chunk);\n }),\n );\n\n const totalLength = chunks.reduce((sum, c) => sum + c.byteLength, 0);\n const buffer = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n buffer.set(chunk, offset);\n offset += chunk.byteLength;\n }\n\n return buffer;\n });\n}\n\n/**\n * Checks if the input is an MPEG-TS format based on the sync byte.\n * MPEG-TS packets start with the sync byte 0x47.\n *\n * @param header - First bytes of the file (at least 1 byte needed)\n * @returns true if the format appears to be MPEG-TS\n */\nexport function isMpegTS(header: Uint8Array): boolean {\n // MPEG-TS sync byte\n return header.length > 0 && header[0] === 0x47;\n}\n\n/**\n * Checks if a MIME type indicates MPEG-TS format.\n *\n * @param mimeType - The MIME type to check\n * @returns true if the MIME type indicates MPEG-TS\n */\nexport function isMpegTSMimeType(mimeType: string | undefined): boolean {\n if (!mimeType) return false;\n const normalizedType = mimeType.toLowerCase();\n return (\n normalizedType === \"video/mp2t\" ||\n normalizedType === \"video/mpeg-ts\" ||\n normalizedType === \"video/mpegts\" ||\n normalizedType === \"application/x-mpegts\"\n );\n}\n","import { UploadistaError } from \"@uploadista/core/errors\";\nimport type {\n DescribeVideoMetadata,\n VideoPluginShape,\n VideoStreamInput,\n VideoStreamOptions,\n} from \"@uploadista/core/flow\";\nimport { withOperationSpan } from \"@uploadista/observability\";\nimport { Effect, type Stream } from \"effect\";\nimport { Decoder, Demuxer, Encoder, Muxer } from \"node-av/api\";\nimport {\n audioCodecToAVName,\n codecToAVName,\n imageFormatToEncoder,\n} from \"./utils/format-mappings\";\nimport { createMemoryOutput } from \"./utils/memory-io\";\nimport {\n collectStreamToBuffer,\n createStreamingOutput,\n isMpegTSMimeType,\n} from \"./utils/streaming-io\";\n\n/**\n * Helper to check if a VideoStreamInput is a Stream vs a Uint8Array.\n * Streams have a pipe property that is a function.\n */\nfunction isInputStream(\n input: VideoStreamInput,\n): input is Stream.Stream<Uint8Array, UploadistaError> {\n return (\n typeof input === \"object\" &&\n input !== null &&\n \"pipe\" in input &&\n typeof (input as { pipe: unknown }).pipe === \"function\"\n );\n}\n\n/**\n * Creates a node-av based video processing plugin\n */\nexport function createVideoPlugin(): VideoPluginShape {\n return {\n describe: (input) =>\n Effect.tryPromise({\n try: async () => {\n // Convert Uint8Array to Buffer for node-av\n const buffer = Buffer.from(input);\n await using mediaInput = await Demuxer.open(buffer);\n\n const videoStream = mediaInput.video();\n const audioStream = mediaInput.audio();\n\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n const videoCodecParams = videoStream.codecpar;\n\n // Calculate frame rate from rational number\n let frameRate = 0;\n if (videoStream.rFrameRate) {\n const { num, den } = videoStream.rFrameRate;\n frameRate = den ? num / den : num;\n }\n\n // Get aspect ratio\n let aspectRatio = \"unknown\";\n if (videoStream.sampleAspectRatio) {\n const { num, den } = videoStream.sampleAspectRatio;\n aspectRatio = `${num}:${den}`;\n }\n\n const metadata: DescribeVideoMetadata = {\n duration: mediaInput.duration || 0,\n width: videoCodecParams.width || 0,\n height: videoCodecParams.height || 0,\n codec: String(videoCodecParams.codecId) || \"unknown\",\n format: mediaInput.formatName || \"unknown\",\n bitrate: mediaInput.bitRate || 0,\n frameRate,\n aspectRatio,\n hasAudio: !!audioStream,\n audioCodec: audioStream?.codecpar.codecId\n ? String(audioStream.codecpar.codecId)\n : undefined,\n audioBitrate: audioStream?.codecpar.bitRate\n ? Number(audioStream.codecpar.bitRate)\n : undefined,\n size: input.byteLength,\n };\n\n return metadata;\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_METADATA_EXTRACTION_FAILED\", {\n body: `Failed to extract video metadata: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n }).pipe(\n withOperationSpan(\"video\", \"describe\", {\n \"video.input_size\": input.byteLength,\n }),\n ),\n\n transcode: (input, options) =>\n Effect.tryPromise({\n try: async () => {\n // Convert Uint8Array to Buffer for input\n const inputBuffer = Buffer.from(input);\n\n // Create in-memory output\n const { callbacks, getOutput } = createMemoryOutput();\n\n await using mediaInput = await Demuxer.open(inputBuffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: options.format,\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n\n // Determine encoder codec\n const encoderCodec = options.codec\n ? codecToAVName[options.codec]\n : codecToAVName.h264;\n\n using videoEncoder = await Encoder.create(encoderCodec, {\n ...(options.videoBitrate && { bitrate: options.videoBitrate }),\n });\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n // Process video frames\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n // Flush remaining packets\n await videoEncoder.flush();\n let transcodeVPacket = await videoEncoder.receive();\n while (transcodeVPacket) {\n await mediaOutput.writePacket(transcodeVPacket, videoOutputIndex);\n transcodeVPacket.free();\n transcodeVPacket = await videoEncoder.receive();\n }\n\n // Handle audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n\n const audioEncoderCodec = options.audioCodec\n ? audioCodecToAVName[options.audioCodec]\n : audioCodecToAVName.aac;\n\n using audioEncoder = await Encoder.create(audioEncoderCodec, {\n ...(options.audioBitrate && { bitrate: options.audioBitrate }),\n });\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n // Process audio frames\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n // Flush remaining packets\n await audioEncoder.flush();\n let transcodeAPacket = await audioEncoder.receive();\n while (transcodeAPacket) {\n await mediaOutput.writePacket(transcodeAPacket, audioOutputIndex);\n transcodeAPacket.free();\n transcodeAPacket = await audioEncoder.receive();\n }\n }\n\n // Return accumulated output\n return getOutput();\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Transcode failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n }).pipe(\n withOperationSpan(\"video\", \"transcode\", {\n \"video.format\": options.format,\n \"video.codec\": options.codec,\n \"video.video_bitrate\": options.videoBitrate,\n \"video.audio_codec\": options.audioCodec,\n \"video.audio_bitrate\": options.audioBitrate,\n \"video.input_size\": input.byteLength,\n }),\n ),\n\n resize: (input, options) =>\n Effect.tryPromise({\n try: async () => {\n // Convert Uint8Array to Buffer for input\n const inputBuffer = Buffer.from(input);\n\n // Create in-memory output\n const { callbacks, getOutput } = createMemoryOutput();\n\n await using mediaInput = await Demuxer.open(inputBuffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: \"mp4\",\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n\n // TODO: Implement proper resizing with FilterAPI\n // Currently, resize functionality is limited because node-av's Encoder\n // auto-initializes from the first frame it receives. To implement proper\n // resizing, we would need to:\n // 1. Use FilterAPI.create('scale', { width, height }) to create a scale filter\n // 2. Pass decoded frames through the filter before encoding\n // For now, this function will pass through frames without resizing.\n\n // Validate that resize parameters are provided\n if (!options.width && !options.height) {\n throw new Error(\"Either width or height must be specified\");\n }\n\n using videoEncoder = await Encoder.create(codecToAVName.h264);\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n // Process video frames with resizing\n // Note: For production use, consider using FilterAPI for better quality scaling\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n // TODO: Apply scale filter here for better quality\n // For now, encoder will handle basic resizing\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n // Flush remaining packets\n await videoEncoder.flush();\n let vPacket = await videoEncoder.receive();\n while (vPacket) {\n await mediaOutput.writePacket(vPacket, videoOutputIndex);\n vPacket.free();\n vPacket = await videoEncoder.receive();\n }\n\n // Copy audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n using audioEncoder = await Encoder.create(audioCodecToAVName.aac);\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n // Flush remaining packets\n await audioEncoder.flush();\n let resizeAPacket = await audioEncoder.receive();\n while (resizeAPacket) {\n await mediaOutput.writePacket(resizeAPacket, audioOutputIndex);\n resizeAPacket.free();\n resizeAPacket = await audioEncoder.receive();\n }\n }\n\n // Return accumulated output\n return getOutput();\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Resize failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n }).pipe(\n withOperationSpan(\"video\", \"resize\", {\n \"video.width\": options.width,\n \"video.height\": options.height,\n \"video.input_size\": input.byteLength,\n }),\n ),\n\n trim: (input, options) =>\n Effect.tryPromise({\n try: async () => {\n // Convert Uint8Array to Buffer for input\n const inputBuffer = Buffer.from(input);\n\n // Create in-memory output\n const { callbacks, getOutput } = createMemoryOutput();\n\n await using mediaInput = await Demuxer.open(inputBuffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: \"mp4\",\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n // Calculate end time\n let endTime: number;\n if (options.duration !== undefined) {\n endTime = options.startTime + options.duration;\n } else if (options.endTime !== undefined) {\n endTime = options.endTime;\n } else {\n endTime = mediaInput.duration || Number.POSITIVE_INFINITY;\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n using videoEncoder = await Encoder.create(codecToAVName.h264);\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n // Process video frames within time range\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n // Calculate frame timestamp\n const pts = frame.pts || 0n;\n const timeBase = videoStream.timeBase\n ? videoStream.timeBase.num / videoStream.timeBase.den\n : 1;\n const timestamp = Number(pts) * timeBase;\n\n if (timestamp >= options.startTime && timestamp < endTime) {\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n if (timestamp >= endTime) break;\n }\n\n // Flush remaining packets\n await videoEncoder.flush();\n let trimVPacket = await videoEncoder.receive();\n while (trimVPacket) {\n await mediaOutput.writePacket(trimVPacket, videoOutputIndex);\n trimVPacket.free();\n trimVPacket = await videoEncoder.receive();\n }\n\n // Handle audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n using audioEncoder = await Encoder.create(audioCodecToAVName.aac);\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n const pts = frame.pts || 0n;\n const timeBase = audioStream.timeBase\n ? audioStream.timeBase.num / audioStream.timeBase.den\n : 1;\n const timestamp = Number(pts) * timeBase;\n\n if (timestamp >= options.startTime && timestamp < endTime) {\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n if (timestamp >= endTime) break;\n }\n\n // Flush remaining packets\n await audioEncoder.flush();\n let trimAPacket = await audioEncoder.receive();\n while (trimAPacket) {\n await mediaOutput.writePacket(trimAPacket, audioOutputIndex);\n trimAPacket.free();\n trimAPacket = await audioEncoder.receive();\n }\n }\n\n // Return accumulated output\n return getOutput();\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Trim failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n }).pipe(\n withOperationSpan(\"video\", \"trim\", {\n \"video.start_time\": options.startTime,\n \"video.end_time\": options.endTime,\n \"video.duration\": options.duration,\n \"video.input_size\": input.byteLength,\n }),\n ),\n\n extractFrame: (input, options) =>\n Effect.tryPromise({\n try: async () => {\n // Convert Uint8Array to Buffer for input\n const inputBuffer = Buffer.from(input);\n const format = options.format || \"jpeg\";\n\n await using mediaInput = await Demuxer.open(inputBuffer);\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n using decoder = await Decoder.create(videoStream);\n\n let frameData: Uint8Array | null = null;\n const targetTimestamp = options.timestamp;\n\n for await (using frame of decoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n // Calculate frame timestamp\n const pts = frame.pts || 0n;\n const timeBase = videoStream.timeBase\n ? videoStream.timeBase.num / videoStream.timeBase.den\n : 1;\n const timestamp = Number(pts) * timeBase;\n\n // Look for frame at or after target timestamp\n if (timestamp >= targetTimestamp) {\n // Use an image encoder to save the frame\n const encoderCodec =\n imageFormatToEncoder[format] || imageFormatToEncoder.jpeg;\n using imageEncoder = await Encoder.create(encoderCodec);\n\n // Encode the frame as image\n // The encoder will initialize from the first frame's properties\n await imageEncoder.encode(frame);\n const packet = await imageEncoder.receive();\n if (packet?.data) {\n // Convert Buffer to Uint8Array\n frameData = new Uint8Array(packet.data);\n packet.free();\n break;\n }\n }\n }\n\n if (!frameData) {\n throw new Error(`No frame found at timestamp ${targetTimestamp}`);\n }\n\n return frameData;\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Frame extraction failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n }).pipe(\n withOperationSpan(\"video\", \"extract-frame\", {\n \"video.timestamp\": options.timestamp,\n \"video.format\": options.format ?? \"jpeg\",\n \"video.input_size\": input.byteLength,\n }),\n ),\n\n // Streaming support flag\n supportsStreaming: true,\n\n // Streaming transcode implementation\n transcodeStream: (\n input: VideoStreamInput,\n options,\n streamOptions?: VideoStreamOptions,\n ) =>\n Effect.gen(function* () {\n // Convert stream input to buffer if needed\n // Currently MPEG-TS input streaming is not yet implemented\n const inputBuffer: Uint8Array = isInputStream(input)\n ? yield* collectStreamToBuffer(input)\n : input;\n\n // Create streaming output\n const { callbacks, stream, finalize } = createStreamingOutput();\n\n // Start processing in background and return stream immediately\n const processVideo = Effect.tryPromise({\n try: async () => {\n const buffer = Buffer.from(inputBuffer);\n\n await using mediaInput = await Demuxer.open(buffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: options.format,\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n\n const encoderCodec = options.codec\n ? codecToAVName[options.codec]\n : codecToAVName.h264;\n\n using videoEncoder = await Encoder.create(encoderCodec, {\n ...(options.videoBitrate && { bitrate: options.videoBitrate }),\n });\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n // Process video frames\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n // Flush remaining packets\n await videoEncoder.flush();\n let vPacket = await videoEncoder.receive();\n while (vPacket) {\n await mediaOutput.writePacket(vPacket, videoOutputIndex);\n vPacket.free();\n vPacket = await videoEncoder.receive();\n }\n\n // Handle audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n\n const audioEncoderCodec = options.audioCodec\n ? audioCodecToAVName[options.audioCodec]\n : audioCodecToAVName.aac;\n\n using audioEncoder = await Encoder.create(audioEncoderCodec, {\n ...(options.audioBitrate && { bitrate: options.audioBitrate }),\n });\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n await audioEncoder.flush();\n let aPacket = await audioEncoder.receive();\n while (aPacket) {\n await mediaOutput.writePacket(aPacket, audioOutputIndex);\n aPacket.free();\n aPacket = await audioEncoder.receive();\n }\n }\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Streaming transcode failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n });\n\n // Fork processing and finalize when done\n yield* Effect.fork(\n Effect.tap(processVideo, () => Effect.sync(finalize)),\n );\n\n return stream;\n }).pipe(\n withOperationSpan(\"video\", \"transcode-stream\", {\n \"video.format\": options.format,\n \"video.codec\": options.codec,\n \"video.streaming_input\": isMpegTSMimeType(streamOptions?.inputFormat),\n }),\n ),\n\n // Streaming resize implementation\n resizeStream: (\n input: VideoStreamInput,\n options,\n streamOptions?: VideoStreamOptions,\n ) =>\n Effect.gen(function* () {\n // Convert stream input to buffer if needed\n const inputBuffer: Uint8Array = isInputStream(input)\n ? yield* collectStreamToBuffer(input)\n : input;\n\n // Create streaming output\n const { callbacks, stream, finalize } = createStreamingOutput();\n\n const processVideo = Effect.tryPromise({\n try: async () => {\n const buffer = Buffer.from(inputBuffer);\n\n await using mediaInput = await Demuxer.open(buffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: \"mp4\",\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n if (!options.width && !options.height) {\n throw new Error(\"Either width or height must be specified\");\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n using videoEncoder = await Encoder.create(codecToAVName.h264);\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n await videoEncoder.flush();\n let vPacket = await videoEncoder.receive();\n while (vPacket) {\n await mediaOutput.writePacket(vPacket, videoOutputIndex);\n vPacket.free();\n vPacket = await videoEncoder.receive();\n }\n\n // Copy audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n using audioEncoder = await Encoder.create(audioCodecToAVName.aac);\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n await audioEncoder.flush();\n let aPacket = await audioEncoder.receive();\n while (aPacket) {\n await mediaOutput.writePacket(aPacket, audioOutputIndex);\n aPacket.free();\n aPacket = await audioEncoder.receive();\n }\n }\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Streaming resize failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n });\n\n yield* Effect.fork(\n Effect.tap(processVideo, () => Effect.sync(finalize)),\n );\n\n return stream;\n }).pipe(\n withOperationSpan(\"video\", \"resize-stream\", {\n \"video.width\": options.width,\n \"video.height\": options.height,\n \"video.streaming_input\": isMpegTSMimeType(streamOptions?.inputFormat),\n }),\n ),\n\n // Streaming trim implementation\n trimStream: (\n input: VideoStreamInput,\n options,\n streamOptions?: VideoStreamOptions,\n ) =>\n Effect.gen(function* () {\n // Convert stream input to buffer if needed\n const inputBuffer: Uint8Array = isInputStream(input)\n ? yield* collectStreamToBuffer(input)\n : input;\n\n // Create streaming output\n const { callbacks, stream, finalize } = createStreamingOutput();\n\n const processVideo = Effect.tryPromise({\n try: async () => {\n const buffer = Buffer.from(inputBuffer);\n\n await using mediaInput = await Demuxer.open(buffer);\n await using mediaOutput = await Muxer.open(callbacks, {\n format: \"mp4\",\n });\n\n const videoStream = mediaInput.video();\n if (!videoStream) {\n throw new Error(\"No video stream found\");\n }\n\n // Calculate end time\n let endTime: number;\n if (options.duration !== undefined) {\n endTime = options.startTime + options.duration;\n } else if (options.endTime !== undefined) {\n endTime = options.endTime;\n } else {\n endTime = mediaInput.duration || Number.POSITIVE_INFINITY;\n }\n\n using videoDecoder = await Decoder.create(videoStream);\n using videoEncoder = await Encoder.create(codecToAVName.h264);\n\n const videoOutputIndex = mediaOutput.addStream(videoEncoder);\n\n for await (using frame of videoDecoder.frames(\n mediaInput.packets(videoStream.index),\n )) {\n if (!frame) continue;\n const pts = frame.pts || 0n;\n const timeBase = videoStream.timeBase\n ? videoStream.timeBase.num / videoStream.timeBase.den\n : 1;\n const timestamp = Number(pts) * timeBase;\n\n if (timestamp >= options.startTime && timestamp < endTime) {\n await videoEncoder.encode(frame);\n let packet = await videoEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n packet = await videoEncoder.receive();\n }\n }\n\n if (timestamp >= endTime) break;\n }\n\n await videoEncoder.flush();\n let vPacket = await videoEncoder.receive();\n while (vPacket) {\n await mediaOutput.writePacket(vPacket, videoOutputIndex);\n vPacket.free();\n vPacket = await videoEncoder.receive();\n }\n\n // Handle audio stream if present\n const audioStream = mediaInput.audio();\n if (audioStream) {\n using audioDecoder = await Decoder.create(audioStream);\n using audioEncoder = await Encoder.create(audioCodecToAVName.aac);\n\n const audioOutputIndex = mediaOutput.addStream(audioEncoder);\n\n for await (using frame of audioDecoder.frames(\n mediaInput.packets(audioStream.index),\n )) {\n if (!frame) continue;\n const pts = frame.pts || 0n;\n const timeBase = audioStream.timeBase\n ? audioStream.timeBase.num / audioStream.timeBase.den\n : 1;\n const timestamp = Number(pts) * timeBase;\n\n if (timestamp >= options.startTime && timestamp < endTime) {\n await audioEncoder.encode(frame);\n let packet = await audioEncoder.receive();\n while (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n packet = await audioEncoder.receive();\n }\n }\n\n if (timestamp >= endTime) break;\n }\n\n await audioEncoder.flush();\n let aPacket = await audioEncoder.receive();\n while (aPacket) {\n await mediaOutput.writePacket(aPacket, audioOutputIndex);\n aPacket.free();\n aPacket = await audioEncoder.receive();\n }\n }\n },\n catch: (error) =>\n UploadistaError.fromCode(\"VIDEO_PROCESSING_FAILED\", {\n body: `Streaming trim failed: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n }),\n });\n\n yield* Effect.fork(\n Effect.tap(processVideo, () => Effect.sync(finalize)),\n );\n\n return stream;\n }).pipe(\n withOperationSpan(\"video\", \"trim-stream\", {\n \"video.start_time\": options.startTime,\n \"video.end_time\": options.endTime,\n \"video.duration\": options.duration,\n \"video.streaming_input\": isMpegTSMimeType(streamOptions?.inputFormat),\n }),\n ),\n };\n}\n","import { VideoPlugin } from \"@uploadista/core/flow\";\nimport { Effect, Layer } from \"effect\";\nimport { checkAVAvailable } from \"./utils/av-check\";\nimport { createVideoPlugin } from \"./video-plugin\";\n\n/**\n * Effect Layer for the node-av video plugin\n *\n * This layer provides video processing capabilities using node-av (FFmpeg bindings).\n * Note: node-av includes prebuilt FFmpeg binaries, so no system installation is required.\n *\n * @example\n * ```typescript\n * import { AVNodeVideoPlugin } from \"@uploadista/flow-videos-av-node\";\n * import { Effect } from \"effect\";\n *\n * const program = Effect.gen(function* () {\n * const videoPlugin = yield* VideoPlugin;\n * const metadata = yield* videoPlugin.describe(videoBytes);\n * return metadata;\n * });\n *\n * // Run with node-av plugin layer\n * const result = await Effect.runPromise(\n * program.pipe(Effect.provide(AVNodeVideoPluginLive))\n * );\n * ```\n */\nexport const videoPlugin = () =>\n Layer.succeed(VideoPlugin, createVideoPlugin());\n\n/**\n * Effect Layer for the node-av video plugin with availability check\n *\n * This layer checks if node-av is properly installed and logs status information.\n * The plugin will still be created, but operations will fail if node-av is not available.\n *\n * @example\n * ```typescript\n * import { AVNodeVideoPluginWithCheck } from \"@uploadista/flow-videos-av-node\";\n * import { Effect } from \"effect\";\n *\n * const program = Effect.gen(function* () {\n * const videoPlugin = yield* VideoPlugin;\n * const metadata = yield* videoPlugin.describe(videoBytes);\n * return metadata;\n * });\n *\n * // Run with node-av plugin layer (with check)\n * const result = await Effect.runPromise(\n * program.pipe(Effect.provide(AVNodeVideoPluginWithCheck))\n * );\n * ```\n */\nexport const videoPluginWithCheck = () =>\n Layer.effectDiscard(\n Effect.gen(function* () {\n const result = yield* Effect.promise(() => checkAVAvailable());\n\n if (!result.available) {\n console.warn(\n \"⚠️ node-av is not installed or not available.\",\n \"\\nVideo processing operations will fail.\",\n \"\\nInstall node-av: npm install node-av\",\n );\n } else {\n console.log(`✓ node-av ${result.version} detected`);\n }\n }),\n ).pipe(Layer.provideMerge(videoPlugin()));\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@uploadista/flow-videos-av-node",
3
3
  "type": "module",
4
- "version": "0.0.20-beta.8",
4
+ "version": "0.0.20-beta.9",
5
5
  "description": "FFmpeg video processing plugin for Uploadista Flow with av-node",
6
6
  "license": "MIT",
7
7
  "author": "Uploadista",
@@ -15,8 +15,8 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "node-av": "5.0.3",
18
- "@uploadista/core": "0.0.20-beta.8",
19
- "@uploadista/observability": "0.0.20-beta.8"
18
+ "@uploadista/core": "0.0.20-beta.9",
19
+ "@uploadista/observability": "0.0.20-beta.9"
20
20
  },
21
21
  "peerDependencies": {
22
22
  "effect": "^3.0.0",
@@ -29,7 +29,7 @@
29
29
  "tsdown": "0.18.0",
30
30
  "vitest": "4.0.15",
31
31
  "zod": "4.2.0",
32
- "@uploadista/typescript-config": "0.0.20-beta.8"
32
+ "@uploadista/typescript-config": "0.0.20-beta.9"
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsc --noEmit && tsdown",
package/src/index.ts CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  export * from "./utils/av-check";
4
4
  export * from "./utils/format-mappings";
5
- export { createAVNodeVideoPlugin } from "./video-plugin";
5
+ export { createVideoPlugin } from "./video-plugin";
6
6
  export {
7
- AVNodeVideoPlugin,
8
- AVNodeVideoPluginWithCheck,
7
+ videoPlugin,
8
+ videoPluginWithCheck,
9
9
  } from "./video-plugin-layer";
@@ -1,7 +1,7 @@
1
1
  import { VideoPlugin } from "@uploadista/core/flow";
2
2
  import { Effect, Layer } from "effect";
3
3
  import { checkAVAvailable } from "./utils/av-check";
4
- import { createAVNodeVideoPlugin } from "./video-plugin";
4
+ import { createVideoPlugin } from "./video-plugin";
5
5
 
6
6
  /**
7
7
  * Effect Layer for the node-av video plugin
@@ -26,10 +26,8 @@ import { createAVNodeVideoPlugin } from "./video-plugin";
26
26
  * );
27
27
  * ```
28
28
  */
29
- export const AVNodeVideoPlugin = Layer.succeed(
30
- VideoPlugin,
31
- createAVNodeVideoPlugin(),
32
- );
29
+ export const videoPlugin = () =>
30
+ Layer.succeed(VideoPlugin, createVideoPlugin());
33
31
 
34
32
  /**
35
33
  * Effect Layer for the node-av video plugin with availability check
@@ -54,18 +52,19 @@ export const AVNodeVideoPlugin = Layer.succeed(
54
52
  * );
55
53
  * ```
56
54
  */
57
- export const AVNodeVideoPluginWithCheck = Layer.effectDiscard(
58
- Effect.gen(function* () {
59
- const result = yield* Effect.promise(() => checkAVAvailable());
55
+ export const videoPluginWithCheck = () =>
56
+ Layer.effectDiscard(
57
+ Effect.gen(function* () {
58
+ const result = yield* Effect.promise(() => checkAVAvailable());
60
59
 
61
- if (!result.available) {
62
- console.warn(
63
- "⚠️ node-av is not installed or not available.",
64
- "\nVideo processing operations will fail.",
65
- "\nInstall node-av: npm install node-av",
66
- );
67
- } else {
68
- console.log(`✓ node-av ${result.version} detected`);
69
- }
70
- }),
71
- ).pipe(Layer.provideMerge(AVNodeVideoPlugin));
60
+ if (!result.available) {
61
+ console.warn(
62
+ "⚠️ node-av is not installed or not available.",
63
+ "\nVideo processing operations will fail.",
64
+ "\nInstall node-av: npm install node-av",
65
+ );
66
+ } else {
67
+ console.log(`✓ node-av ${result.version} detected`);
68
+ }
69
+ }),
70
+ ).pipe(Layer.provideMerge(videoPlugin()));
@@ -38,7 +38,7 @@ function isInputStream(
38
38
  /**
39
39
  * Creates a node-av based video processing plugin
40
40
  */
41
- export function createAVNodeVideoPlugin(): VideoPluginShape {
41
+ export function createVideoPlugin(): VideoPluginShape {
42
42
  return {
43
43
  describe: (input) =>
44
44
  Effect.tryPromise({