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

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.17-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.17-beta.9 build /Users/denislaboureyras/Documents/uploadista/dev/uploadista-workspace/uploadista-sdk/packages/flow/videos/av-node
4
4
  > tsdown
5
5
 
6
6
  ℹ tsdown v0.16.6 powered by rolldown v1.0.0-beta.51
@@ -9,14 +9,14 @@
9
9
  ℹ tsconfig: tsconfig.json
10
10
  ℹ Build start
11
11
  ℹ Cleaning 7 files
12
- ℹ [CJS] dist/index.cjs 6.96 kB │ gzip: 2.18 kB
13
- ℹ [CJS] 1 files, total: 6.96 kB
14
- ℹ [ESM] dist/index.mjs  6.77 kB │ gzip: 2.29 kB
15
- ℹ [ESM] dist/index.mjs.map 31.63 kB │ gzip: 6.47 kB
12
+ ℹ [CJS] dist/index.cjs 7.28 kB │ gzip: 2.22 kB
13
+ ℹ [CJS] 1 files, total: 7.28 kB
14
+ ℹ [ESM] dist/index.mjs  7.09 kB │ gzip: 2.32 kB
15
+ ℹ [ESM] dist/index.mjs.map 32.57 kB │ gzip: 6.47 kB
16
16
  ℹ [ESM] dist/index.d.mts.map  0.88 kB │ gzip: 0.47 kB
17
17
  ℹ [ESM] dist/index.d.mts  3.39 kB │ gzip: 1.04 kB
18
- ℹ [ESM] 4 files, total: 42.67 kB
18
+ ℹ [ESM] 4 files, total: 43.94 kB
19
19
  ℹ [CJS] dist/index.d.cts.map 0.88 kB │ gzip: 0.47 kB
20
20
  ℹ [CJS] dist/index.d.cts 3.39 kB │ gzip: 1.04 kB
21
21
  ℹ [CJS] 2 files, total: 4.28 kB
22
- ✔ Build complete in 5936ms
22
+ ✔ Build complete in 4649ms
package/dist/index.cjs CHANGED
@@ -1,3 +1,3 @@
1
- let e=require(`node-av/constants`),t=require(`@uploadista/core/errors`),n=require(`effect`),r=require(`node-av/api`),i=require(`@uploadista/core/flow`);async function a(){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 o={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},s={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`},c={h264:e.FF_ENCODER_LIBX264,h265:e.FF_ENCODER_LIBX265,vp9:e.FF_ENCODER_LIBVPX_VP9,av1:e.FF_ENCODER_LIBAOM_AV1},l={aac:e.FF_ENCODER_AAC,mp3:e.FF_ENCODER_LIBMP3LAME,opus:e.FF_ENCODER_LIBOPUS,vorbis:e.FF_ENCODER_LIBVORBIS},u={jpeg:e.FF_ENCODER_MJPEG,mjpeg:e.FF_ENCODER_MJPEG,png:e.FF_ENCODER_PNG};function d(){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 f(){return{describe:e=>n.Effect.tryPromise({try:async()=>{let t=Buffer.from(e);await using n=await r.Demuxer.open(t);let i=n.video(),a=n.audio();if(!i)throw Error(`No video stream found`);let o=i.codecpar,s=0;if(i.rFrameRate){let{num:e,den:t}=i.rFrameRate;s=t?e/t:e}let c=`unknown`;if(i.sampleAspectRatio){let{num:e,den:t}=i.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})}),transcode:(e,i)=>n.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:a}=d();await using o=await r.Demuxer.open(t),s=await r.Muxer.open(n,{format:i.format});let u=o.video();if(!u)throw Error(`No video stream found`);using f=await r.Decoder.create(u);let p=i.codec?c[i.codec]:c.h264;using m=await r.Encoder.create(p,{...i.videoBitrate&&{bitrate:i.videoBitrate}});let h=s.addStream(m);for await(using e of f.frames(o.packets(u.index))){let t=await m.encode(e);t&&(await s.writePacket(t,h),t.free())}await m.flush();let g=await m.receive();for(;g!==null;)await s.writePacket(g,h),g.free(),g=await m.receive();let _=o.audio();if(_){using e=await r.Decoder.create(_);let t=i.audioCodec?l[i.audioCodec]:l.aac;using n=await r.Encoder.create(t,{...i.audioBitrate&&{bitrate:i.audioBitrate}});let a=s.addStream(n);for await(using t of e.frames(o.packets(_.index))){let e=await n.encode(t);e&&(await s.writePacket(e,a),e.free())}await n.flush();let c=await n.receive();for(;c!==null;)await s.writePacket(c,a),c.free(),c=await n.receive()}return a()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Transcode failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}),resize:(e,i)=>n.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:a}=d();await using o=await r.Demuxer.open(t),s=await r.Muxer.open(n,{format:`mp4`});let u=o.video();if(!u)throw Error(`No video stream found`);using f=await r.Decoder.create(u);if(!i.width&&!i.height)throw Error(`Either width or height must be specified`);using p=await r.Encoder.create(c.h264);let m=s.addStream(p);for await(using e of f.frames(o.packets(u.index))){let t=await p.encode(e);t&&(await s.writePacket(t,m),t.free())}await p.flush();let h=await p.receive();for(;h!==null;)await s.writePacket(h,m),h.free(),h=await p.receive();let g=o.audio();if(g){using e=await r.Decoder.create(g),t=await r.Encoder.create(l.aac);let n=s.addStream(t);for await(using r of e.frames(o.packets(g.index))){let e=await t.encode(r);e&&(await s.writePacket(e,n),e.free())}await t.flush();let i=await t.receive();for(;i!==null;)await s.writePacket(i,n),i.free(),i=await t.receive()}return a()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Resize failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}),trim:(e,i)=>n.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:a}=d();await using o=await r.Demuxer.open(t),s=await r.Muxer.open(n,{format:`mp4`});let u=o.video();if(!u)throw Error(`No video stream found`);let f;f=i.duration===void 0?i.endTime===void 0?o.duration||1/0:i.endTime:i.startTime+i.duration;using p=await r.Decoder.create(u),m=await r.Encoder.create(c.h264);let h=s.addStream(m);for await(using e of p.frames(o.packets(u.index))){let t=e?.pts||0n,n=u.timeBase?u.timeBase.num/u.timeBase.den:1,r=Number(t)*n;if(r>=i.startTime&&r<f){let t=await m.encode(e);t&&(await s.writePacket(t,h),t.free())}if(r>=f)break}await m.flush();let g=await m.receive();for(;g!==null;)await s.writePacket(g,h),g.free(),g=await m.receive();let _=o.audio();if(_){using e=await r.Decoder.create(_),t=await r.Encoder.create(l.aac);let n=s.addStream(t);for await(using r of e.frames(o.packets(_.index))){let e=r?.pts||0n,a=_.timeBase?_.timeBase.num/_.timeBase.den:1,o=Number(e)*a;if(o>=i.startTime&&o<f){let e=await t.encode(r);e&&(await s.writePacket(e,n),e.free())}if(o>=f)break}await t.flush();let a=await t.receive();for(;a!==null;)await s.writePacket(a,n),a.free(),a=await t.receive()}return a()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Trim failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}),extractFrame:(e,i)=>n.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),n=i.format||`jpeg`;await using a=await r.Demuxer.open(t);let o=a.video();if(!o)throw Error(`No video stream found`);using s=await r.Decoder.create(o);let c=null,l=i.timestamp;for await(using e of s.frames(a.packets(o.index))){let t=e?.pts||0n,i=o.timeBase?o.timeBase.num/o.timeBase.den:1;if(Number(t)*i>=l){let t=u[n]||u.jpeg;using i=await r.Encoder.create(t);let a=await i.encode(e);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})})}}const p=n.Layer.succeed(i.VideoPlugin,f()),m=n.Layer.effectDiscard(n.Effect.gen(function*(){let e=yield*n.Effect.promise(()=>a());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(`effect`),r=require(`node-av/api`),i=require(`@uploadista/core/flow`);async function a(){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 o={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},s={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`},c={h264:e.FF_ENCODER_LIBX264,h265:e.FF_ENCODER_LIBX265,vp9:e.FF_ENCODER_LIBVPX_VP9,av1:e.FF_ENCODER_LIBAOM_AV1},l={aac:e.FF_ENCODER_AAC,mp3:e.FF_ENCODER_LIBMP3LAME,opus:e.FF_ENCODER_LIBOPUS,vorbis:e.FF_ENCODER_LIBVORBIS},u={jpeg:e.FF_ENCODER_MJPEG,mjpeg:e.FF_ENCODER_MJPEG,png:e.FF_ENCODER_PNG};function d(){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 f(){return{describe:e=>n.Effect.tryPromise({try:async()=>{let t=Buffer.from(e);await using n=await r.Demuxer.open(t);let i=n.video(),a=n.audio();if(!i)throw Error(`No video stream found`);let o=i.codecpar,s=0;if(i.rFrameRate){let{num:e,den:t}=i.rFrameRate;s=t?e/t:e}let c=`unknown`;if(i.sampleAspectRatio){let{num:e,den:t}=i.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})}),transcode:(e,i)=>n.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:a}=d();await using o=await r.Demuxer.open(t),s=await r.Muxer.open(n,{format:i.format});let u=o.video();if(!u)throw Error(`No video stream found`);using f=await r.Decoder.create(u);let p=i.codec?c[i.codec]:c.h264;using m=await r.Encoder.create(p,{...i.videoBitrate&&{bitrate:i.videoBitrate}});let h=s.addStream(m);for await(using e of f.frames(o.packets(u.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 r.Decoder.create(_);let t=i.audioCodec?l[i.audioCodec]:l.aac;using n=await r.Encoder.create(t,{...i.audioBitrate&&{bitrate:i.audioBitrate}});let a=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,a),e.free(),e=await n.receive()}await n.flush();let c=await n.receive();for(;c;)await s.writePacket(c,a),c.free(),c=await n.receive()}return a()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Transcode failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}),resize:(e,i)=>n.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:a}=d();await using o=await r.Demuxer.open(t),s=await r.Muxer.open(n,{format:`mp4`});let u=o.video();if(!u)throw Error(`No video stream found`);using f=await r.Decoder.create(u);if(!i.width&&!i.height)throw Error(`Either width or height must be specified`);using p=await r.Encoder.create(c.h264);let m=s.addStream(p);for await(using e of f.frames(o.packets(u.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 r.Decoder.create(g),t=await r.Encoder.create(l.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 i=await t.receive();for(;i;)await s.writePacket(i,n),i.free(),i=await t.receive()}return a()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Resize failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}),trim:(e,i)=>n.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),{callbacks:n,getOutput:a}=d();await using o=await r.Demuxer.open(t),s=await r.Muxer.open(n,{format:`mp4`});let u=o.video();if(!u)throw Error(`No video stream found`);let f;f=i.duration===void 0?i.endTime===void 0?o.duration||1/0:i.endTime:i.startTime+i.duration;using p=await r.Decoder.create(u),m=await r.Encoder.create(c.h264);let h=s.addStream(m);for await(using e of p.frames(o.packets(u.index))){if(!e)continue;let t=e.pts||0n,n=u.timeBase?u.timeBase.num/u.timeBase.den:1,r=Number(t)*n;if(r>=i.startTime&&r<f){await m.encode(e);let t=await m.receive();for(;t;)await s.writePacket(t,h),t.free(),t=await m.receive()}if(r>=f)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 r.Decoder.create(_),t=await r.Encoder.create(l.aac);let n=s.addStream(t);for await(using r of e.frames(o.packets(_.index))){if(!r)continue;let e=r.pts||0n,a=_.timeBase?_.timeBase.num/_.timeBase.den:1,o=Number(e)*a;if(o>=i.startTime&&o<f){await t.encode(r);let e=await t.receive();for(;e;)await s.writePacket(e,n),e.free(),e=await t.receive()}if(o>=f)break}await t.flush();let a=await t.receive();for(;a;)await s.writePacket(a,n),a.free(),a=await t.receive()}return a()},catch:e=>t.UploadistaError.fromCode(`VIDEO_PROCESSING_FAILED`,{body:`Trim failed: ${e instanceof Error?e.message:String(e)}`,cause:e})}),extractFrame:(e,i)=>n.Effect.tryPromise({try:async()=>{let t=Buffer.from(e),n=i.format||`jpeg`;await using a=await r.Demuxer.open(t);let o=a.video();if(!o)throw Error(`No video stream found`);using s=await r.Decoder.create(o);let c=null,l=i.timestamp;for await(using e of s.frames(a.packets(o.index))){if(!e)continue;let t=e.pts||0n,i=o.timeBase?o.timeBase.num/o.timeBase.den:1;if(Number(t)*i>=l){let t=u[n]||u.jpeg;using i=await r.Encoder.create(t);await i.encode(e);let a=await i.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})})}}const p=n.Layer.succeed(i.VideoPlugin,f()),m=n.Layer.effectDiscard(n.Effect.gen(function*(){let e=yield*n.Effect.promise(()=>a());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
3
  Install node-av: npm install node-av`)})).pipe(n.Layer.provideMerge(p));exports.AVNodeVideoPlugin=p,exports.AVNodeVideoPluginWithCheck=m,exports.audioCodecToAVName=l,exports.checkAVAvailable=a,exports.codecToAVName=c,exports.createAVNodeVideoPlugin=f,exports.formatToExtension=s,exports.formatToMimeType=o,exports.imageFormatToEncoder=u;
@@ -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;;;;;;iBCrCc,uBAAA,CAAA,GAA2B;;;;;;AFf3C;AAUA;;;;ACKA;AAWA;AAWA;;;;;;AAaA;;;;;;AAaA;cEtCa,mBAAiB,KAAA,CAAA,MAAA;;;ADV9B;;;;ACUA;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;;;;;;iBCtCc,uBAAA,CAAA,GAA2B;;;;;;AFd3C;AAUA;;;;ACKA;AAWA;AAWA;;;;;;AAaA;;;;;;AAaA;cEtCa,mBAAiB,KAAA,CAAA,MAAA;;;ADX9B;;;;ACWA;AA4BA;;;;;;;;;;;;;;;;cAAa,4BAA0B,KAAA,CAAA,MAAA"}
@@ -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;;;;;;iBCrCc,uBAAA,CAAA,GAA2B;;;;;;AFf3C;AAUA;;;;ACKA;AAWA;AAWA;;;;;;AAaA;;;;;;AAaA;cEtCa,mBAAiB,KAAA,CAAA,MAAA;;;ADV9B;;;;ACUA;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;;;;;;iBCtCc,uBAAA,CAAA,GAA2B;;;;;;AFd3C;AAUA;;;;ACKA;AAWA;AAWA;;;;;;AAaA;;;;;;AAaA;cEtCa,mBAAiB,KAAA,CAAA,MAAA;;;ADX9B;;;;ACWA;AA4BA;;;;;;;;;;;;;;;;cAAa,4BAA0B,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{Effect as d,Layer as f}from"effect";import{Decoder as p,Demuxer as m,Encoder as h,Muxer as g}from"node-av/api";import{VideoPlugin as _}from"@uploadista/core/flow";async function v(){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 y={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},b={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`},x={h264:o,h265:s,vp9:a,av1:t},S={aac:e,mp3:n,opus:r,vorbis:i},C={jpeg:c,mjpeg:c,png:l};function w(){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 T(){return{describe:e=>d.tryPromise({try:async()=>{let t=Buffer.from(e);await using n=await m.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})}),transcode:(e,t)=>d.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=w();await using a=await m.open(n),o=await g.open(r,{format:t.format});let s=a.video();if(!s)throw Error(`No video stream found`);using c=await p.create(s);let l=t.codec?x[t.codec]:x.h264;using u=await h.create(l,{...t.videoBitrate&&{bitrate:t.videoBitrate}});let d=o.addStream(u);for await(using e of c.frames(a.packets(s.index))){let t=await u.encode(e);t&&(await o.writePacket(t,d),t.free())}await u.flush();let f=await u.receive();for(;f!==null;)await o.writePacket(f,d),f.free(),f=await u.receive();let _=a.audio();if(_){using e=await p.create(_);let n=t.audioCodec?S[t.audioCodec]:S.aac;using r=await h.create(n,{...t.audioBitrate&&{bitrate:t.audioBitrate}});let i=o.addStream(r);for await(using t of e.frames(a.packets(_.index))){let e=await r.encode(t);e&&(await o.writePacket(e,i),e.free())}await r.flush();let s=await r.receive();for(;s!==null;)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})}),resize:(e,t)=>d.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=w();await using a=await m.open(n),o=await g.open(r,{format:`mp4`});let s=a.video();if(!s)throw Error(`No video stream found`);using c=await p.create(s);if(!t.width&&!t.height)throw Error(`Either width or height must be specified`);using l=await h.create(x.h264);let u=o.addStream(l);for await(using e of c.frames(a.packets(s.index))){let t=await l.encode(e);t&&(await o.writePacket(t,u),t.free())}await l.flush();let d=await l.receive();for(;d!==null;)await o.writePacket(d,u),d.free(),d=await l.receive();let f=a.audio();if(f){using e=await p.create(f),t=await h.create(S.aac);let n=o.addStream(t);for await(using r of e.frames(a.packets(f.index))){let e=await t.encode(r);e&&(await o.writePacket(e,n),e.free())}await t.flush();let r=await t.receive();for(;r!==null;)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})}),trim:(e,t)=>d.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=w();await using a=await m.open(n),o=await g.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 p.create(s),u=await h.create(x.h264);let d=o.addStream(u);for await(using e of l.frames(a.packets(s.index))){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){let t=await u.encode(e);t&&(await o.writePacket(t,d),t.free())}if(i>=c)break}await u.flush();let f=await u.receive();for(;f!==null;)await o.writePacket(f,d),f.free(),f=await u.receive();let _=a.audio();if(_){using e=await p.create(_),n=await h.create(S.aac);let r=o.addStream(n);for await(using i of e.frames(a.packets(_.index))){let e=i?.pts||0n,a=_.timeBase?_.timeBase.num/_.timeBase.den:1,s=Number(e)*a;if(s>=t.startTime&&s<c){let e=await n.encode(i);e&&(await o.writePacket(e,r),e.free())}if(s>=c)break}await n.flush();let i=await n.receive();for(;i!==null;)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})}),extractFrame:(e,t)=>d.tryPromise({try:async()=>{let n=Buffer.from(e),r=t.format||`jpeg`;await using i=await m.open(n);let a=i.video();if(!a)throw Error(`No video stream found`);using o=await p.create(a);let s=null,c=t.timestamp;for await(using e of o.frames(i.packets(a.index))){let t=e?.pts||0n,n=a.timeBase?a.timeBase.num/a.timeBase.den:1;if(Number(t)*n>=c){let t=C[r]||C.jpeg;using n=await h.create(t);let i=await n.encode(e);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})})}}const E=f.succeed(_,T()),D=f.effectDiscard(d.gen(function*(){let e=yield*d.promise(()=>v());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{Effect as d,Layer as f}from"effect";import{Decoder as p,Demuxer as m,Encoder as h,Muxer as g}from"node-av/api";import{VideoPlugin as _}from"@uploadista/core/flow";async function v(){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 y={mp4:`video/mp4`,webm:`video/webm`,mov:`video/quicktime`,avi:`video/x-msvideo`},b={mp4:`mp4`,webm:`webm`,mov:`mov`,avi:`avi`},x={h264:o,h265:s,vp9:a,av1:t},S={aac:e,mp3:n,opus:r,vorbis:i},C={jpeg:c,mjpeg:c,png:l};function w(){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 T(){return{describe:e=>d.tryPromise({try:async()=>{let t=Buffer.from(e);await using n=await m.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})}),transcode:(e,t)=>d.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=w();await using a=await m.open(n),o=await g.open(r,{format:t.format});let s=a.video();if(!s)throw Error(`No video stream found`);using c=await p.create(s);let l=t.codec?x[t.codec]:x.h264;using u=await h.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 _=a.audio();if(_){using e=await p.create(_);let n=t.audioCodec?S[t.audioCodec]:S.aac;using r=await h.create(n,{...t.audioBitrate&&{bitrate:t.audioBitrate}});let i=o.addStream(r);for await(using t of e.frames(a.packets(_.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})}),resize:(e,t)=>d.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=w();await using a=await m.open(n),o=await g.open(r,{format:`mp4`});let s=a.video();if(!s)throw Error(`No video stream found`);using c=await p.create(s);if(!t.width&&!t.height)throw Error(`Either width or height must be specified`);using l=await h.create(x.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 p.create(f),t=await h.create(S.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})}),trim:(e,t)=>d.tryPromise({try:async()=>{let n=Buffer.from(e),{callbacks:r,getOutput:i}=w();await using a=await m.open(n),o=await g.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 p.create(s),u=await h.create(x.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 _=a.audio();if(_){using e=await p.create(_),n=await h.create(S.aac);let r=o.addStream(n);for await(using i of e.frames(a.packets(_.index))){if(!i)continue;let e=i.pts||0n,a=_.timeBase?_.timeBase.num/_.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})}),extractFrame:(e,t)=>d.tryPromise({try:async()=>{let n=Buffer.from(e),r=t.format||`jpeg`;await using i=await m.open(n);let a=i.video();if(!a)throw Error(`No video stream found`);using o=await p.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=C[r]||C.jpeg;using n=await h.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})})}}const E=f.succeed(_,T()),D=f.effectDiscard(d.gen(function*(){let e=yield*d.promise(()=>v());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
3
  Install node-av: npm install node-av`)})).pipe(f.provideMerge(E));export{E as AVNodeVideoPlugin,D as AVNodeVideoPluginWithCheck,S as audioCodecToAVName,v as checkAVAvailable,x as codecToAVName,T as createAVNodeVideoPlugin,b as formatToExtension,y as formatToMimeType,C as imageFormatToEncoder};
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","transcodeVPacket: Packet | null","transcodeAPacket: Packet | null","vPacket: Packet | null","resizeAPacket: Packet | null","endTime: number","trimVPacket: Packet | null","trimAPacket: Packet | null","frameData: Uint8Array | null"],"sources":["../src/utils/av-check.ts","../src/utils/format-mappings.ts","../src/utils/memory-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 type {\n DescribeVideoMetadata,\n VideoPluginShape,\n} from \"@uploadista/core/flow\";\nimport { Effect } from \"effect\";\nimport { Decoder, Demuxer, Encoder, Muxer } from \"node-av/api\";\nimport type { Packet } from \"node-av/lib\";\nimport {\n audioCodecToAVName,\n codecToAVName,\n imageFormatToEncoder,\n} from \"./utils/format-mappings\";\nimport { createMemoryOutput } from \"./utils/memory-io\";\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 }),\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 const packet = await videoEncoder.encode(frame);\n if (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n }\n }\n\n // Flush remaining packets\n await videoEncoder.flush();\n let transcodeVPacket: Packet | null = await videoEncoder.receive();\n while (transcodeVPacket !== null) {\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 const packet = await audioEncoder.encode(frame);\n if (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n }\n }\n\n // Flush remaining packets\n await audioEncoder.flush();\n let transcodeAPacket: Packet | null = await audioEncoder.receive();\n while (transcodeAPacket !== null) {\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 }),\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 // TODO: Apply scale filter here for better quality\n // For now, encoder will handle basic resizing\n const packet = await videoEncoder.encode(frame);\n if (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n }\n }\n\n // Flush remaining packets\n await videoEncoder.flush();\n let vPacket: Packet | null = await videoEncoder.receive();\n while (vPacket !== null) {\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 const packet = await audioEncoder.encode(frame);\n if (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n }\n }\n\n // Flush remaining packets\n await audioEncoder.flush();\n let resizeAPacket: Packet | null = await audioEncoder.receive();\n while (resizeAPacket !== null) {\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 }),\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 // 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 const packet = await videoEncoder.encode(frame);\n if (packet) {\n await mediaOutput.writePacket(packet, videoOutputIndex);\n packet.free();\n }\n }\n\n if (timestamp >= endTime) break;\n }\n\n // Flush remaining packets\n await videoEncoder.flush();\n let trimVPacket: Packet | null = await videoEncoder.receive();\n while (trimVPacket !== null) {\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 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 const packet = await audioEncoder.encode(frame);\n if (packet) {\n await mediaOutput.writePacket(packet, audioOutputIndex);\n packet.free();\n }\n }\n\n if (timestamp >= endTime) break;\n }\n\n // Flush remaining packets\n await audioEncoder.flush();\n let trimAPacket: Packet | null = await audioEncoder.receive();\n while (trimAPacket !== null) {\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 }),\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 // 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 const packet = await imageEncoder.encode(frame);\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 }),\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","endTime: number","frameData: Uint8Array | null"],"sources":["../src/utils/av-check.ts","../src/utils/format-mappings.ts","../src/utils/memory-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 type {\n DescribeVideoMetadata,\n VideoPluginShape,\n} from \"@uploadista/core/flow\";\nimport { Effect } 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\";\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 }),\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 }),\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 }),\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 }),\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 }),\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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"}
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.17-beta.8",
4
+ "version": "0.0.17",
5
5
  "description": "FFmpeg video processing plugin for Uploadista Flow with av-node",
6
6
  "license": "MIT",
7
7
  "author": "Uploadista",
@@ -14,17 +14,17 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "node-av": "4.0.0",
17
+ "node-av": "5.0.1",
18
18
  "effect": "3.19.6",
19
19
  "zod": "4.1.13",
20
- "@uploadista/core": "0.0.17-beta.8"
20
+ "@uploadista/core": "0.0.17"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@effect/vitest": "0.27.0",
24
24
  "@types/node": "24.10.1",
25
25
  "tsdown": "0.16.6",
26
26
  "vitest": "4.0.13",
27
- "@uploadista/typescript-config": "0.0.17-beta.8"
27
+ "@uploadista/typescript-config": "0.0.17"
28
28
  },
29
29
  "scripts": {
30
30
  "build": "tsdown",
@@ -5,7 +5,6 @@ import type {
5
5
  } from "@uploadista/core/flow";
6
6
  import { Effect } from "effect";
7
7
  import { Decoder, Demuxer, Encoder, Muxer } from "node-av/api";
8
- import type { Packet } from "node-av/lib";
9
8
  import {
10
9
  audioCodecToAVName,
11
10
  codecToAVName,
@@ -112,17 +111,20 @@ export function createAVNodeVideoPlugin(): VideoPluginShape {
112
111
  for await (using frame of videoDecoder.frames(
113
112
  mediaInput.packets(videoStream.index),
114
113
  )) {
115
- const packet = await videoEncoder.encode(frame);
116
- if (packet) {
114
+ if (!frame) continue;
115
+ await videoEncoder.encode(frame);
116
+ let packet = await videoEncoder.receive();
117
+ while (packet) {
117
118
  await mediaOutput.writePacket(packet, videoOutputIndex);
118
119
  packet.free();
120
+ packet = await videoEncoder.receive();
119
121
  }
120
122
  }
121
123
 
122
124
  // Flush remaining packets
123
125
  await videoEncoder.flush();
124
- let transcodeVPacket: Packet | null = await videoEncoder.receive();
125
- while (transcodeVPacket !== null) {
126
+ let transcodeVPacket = await videoEncoder.receive();
127
+ while (transcodeVPacket) {
126
128
  await mediaOutput.writePacket(transcodeVPacket, videoOutputIndex);
127
129
  transcodeVPacket.free();
128
130
  transcodeVPacket = await videoEncoder.receive();
@@ -147,17 +149,20 @@ export function createAVNodeVideoPlugin(): VideoPluginShape {
147
149
  for await (using frame of audioDecoder.frames(
148
150
  mediaInput.packets(audioStream.index),
149
151
  )) {
150
- const packet = await audioEncoder.encode(frame);
151
- if (packet) {
152
+ if (!frame) continue;
153
+ await audioEncoder.encode(frame);
154
+ let packet = await audioEncoder.receive();
155
+ while (packet) {
152
156
  await mediaOutput.writePacket(packet, audioOutputIndex);
153
157
  packet.free();
158
+ packet = await audioEncoder.receive();
154
159
  }
155
160
  }
156
161
 
157
162
  // Flush remaining packets
158
163
  await audioEncoder.flush();
159
- let transcodeAPacket: Packet | null = await audioEncoder.receive();
160
- while (transcodeAPacket !== null) {
164
+ let transcodeAPacket = await audioEncoder.receive();
165
+ while (transcodeAPacket) {
161
166
  await mediaOutput.writePacket(transcodeAPacket, audioOutputIndex);
162
167
  transcodeAPacket.free();
163
168
  transcodeAPacket = await audioEncoder.receive();
@@ -217,19 +222,22 @@ export function createAVNodeVideoPlugin(): VideoPluginShape {
217
222
  for await (using frame of videoDecoder.frames(
218
223
  mediaInput.packets(videoStream.index),
219
224
  )) {
225
+ if (!frame) continue;
220
226
  // TODO: Apply scale filter here for better quality
221
227
  // For now, encoder will handle basic resizing
222
- const packet = await videoEncoder.encode(frame);
223
- if (packet) {
228
+ await videoEncoder.encode(frame);
229
+ let packet = await videoEncoder.receive();
230
+ while (packet) {
224
231
  await mediaOutput.writePacket(packet, videoOutputIndex);
225
232
  packet.free();
233
+ packet = await videoEncoder.receive();
226
234
  }
227
235
  }
228
236
 
229
237
  // Flush remaining packets
230
238
  await videoEncoder.flush();
231
- let vPacket: Packet | null = await videoEncoder.receive();
232
- while (vPacket !== null) {
239
+ let vPacket = await videoEncoder.receive();
240
+ while (vPacket) {
233
241
  await mediaOutput.writePacket(vPacket, videoOutputIndex);
234
242
  vPacket.free();
235
243
  vPacket = await videoEncoder.receive();
@@ -246,17 +254,20 @@ export function createAVNodeVideoPlugin(): VideoPluginShape {
246
254
  for await (using frame of audioDecoder.frames(
247
255
  mediaInput.packets(audioStream.index),
248
256
  )) {
249
- const packet = await audioEncoder.encode(frame);
250
- if (packet) {
257
+ if (!frame) continue;
258
+ await audioEncoder.encode(frame);
259
+ let packet = await audioEncoder.receive();
260
+ while (packet) {
251
261
  await mediaOutput.writePacket(packet, audioOutputIndex);
252
262
  packet.free();
263
+ packet = await audioEncoder.receive();
253
264
  }
254
265
  }
255
266
 
256
267
  // Flush remaining packets
257
268
  await audioEncoder.flush();
258
- let resizeAPacket: Packet | null = await audioEncoder.receive();
259
- while (resizeAPacket !== null) {
269
+ let resizeAPacket = await audioEncoder.receive();
270
+ while (resizeAPacket) {
260
271
  await mediaOutput.writePacket(resizeAPacket, audioOutputIndex);
261
272
  resizeAPacket.free();
262
273
  resizeAPacket = await audioEncoder.receive();
@@ -311,18 +322,21 @@ export function createAVNodeVideoPlugin(): VideoPluginShape {
311
322
  for await (using frame of videoDecoder.frames(
312
323
  mediaInput.packets(videoStream.index),
313
324
  )) {
325
+ if (!frame) continue;
314
326
  // Calculate frame timestamp
315
- const pts = frame?.pts || 0n;
327
+ const pts = frame.pts || 0n;
316
328
  const timeBase = videoStream.timeBase
317
329
  ? videoStream.timeBase.num / videoStream.timeBase.den
318
330
  : 1;
319
331
  const timestamp = Number(pts) * timeBase;
320
332
 
321
333
  if (timestamp >= options.startTime && timestamp < endTime) {
322
- const packet = await videoEncoder.encode(frame);
323
- if (packet) {
334
+ await videoEncoder.encode(frame);
335
+ let packet = await videoEncoder.receive();
336
+ while (packet) {
324
337
  await mediaOutput.writePacket(packet, videoOutputIndex);
325
338
  packet.free();
339
+ packet = await videoEncoder.receive();
326
340
  }
327
341
  }
328
342
 
@@ -331,8 +345,8 @@ export function createAVNodeVideoPlugin(): VideoPluginShape {
331
345
 
332
346
  // Flush remaining packets
333
347
  await videoEncoder.flush();
334
- let trimVPacket: Packet | null = await videoEncoder.receive();
335
- while (trimVPacket !== null) {
348
+ let trimVPacket = await videoEncoder.receive();
349
+ while (trimVPacket) {
336
350
  await mediaOutput.writePacket(trimVPacket, videoOutputIndex);
337
351
  trimVPacket.free();
338
352
  trimVPacket = await videoEncoder.receive();
@@ -349,17 +363,20 @@ export function createAVNodeVideoPlugin(): VideoPluginShape {
349
363
  for await (using frame of audioDecoder.frames(
350
364
  mediaInput.packets(audioStream.index),
351
365
  )) {
352
- const pts = frame?.pts || 0n;
366
+ if (!frame) continue;
367
+ const pts = frame.pts || 0n;
353
368
  const timeBase = audioStream.timeBase
354
369
  ? audioStream.timeBase.num / audioStream.timeBase.den
355
370
  : 1;
356
371
  const timestamp = Number(pts) * timeBase;
357
372
 
358
373
  if (timestamp >= options.startTime && timestamp < endTime) {
359
- const packet = await audioEncoder.encode(frame);
360
- if (packet) {
374
+ await audioEncoder.encode(frame);
375
+ let packet = await audioEncoder.receive();
376
+ while (packet) {
361
377
  await mediaOutput.writePacket(packet, audioOutputIndex);
362
378
  packet.free();
379
+ packet = await audioEncoder.receive();
363
380
  }
364
381
  }
365
382
 
@@ -368,8 +385,8 @@ export function createAVNodeVideoPlugin(): VideoPluginShape {
368
385
 
369
386
  // Flush remaining packets
370
387
  await audioEncoder.flush();
371
- let trimAPacket: Packet | null = await audioEncoder.receive();
372
- while (trimAPacket !== null) {
388
+ let trimAPacket = await audioEncoder.receive();
389
+ while (trimAPacket) {
373
390
  await mediaOutput.writePacket(trimAPacket, audioOutputIndex);
374
391
  trimAPacket.free();
375
392
  trimAPacket = await audioEncoder.receive();
@@ -408,8 +425,9 @@ export function createAVNodeVideoPlugin(): VideoPluginShape {
408
425
  for await (using frame of decoder.frames(
409
426
  mediaInput.packets(videoStream.index),
410
427
  )) {
428
+ if (!frame) continue;
411
429
  // Calculate frame timestamp
412
- const pts = frame?.pts || 0n;
430
+ const pts = frame.pts || 0n;
413
431
  const timeBase = videoStream.timeBase
414
432
  ? videoStream.timeBase.num / videoStream.timeBase.den
415
433
  : 1;
@@ -424,7 +442,8 @@ export function createAVNodeVideoPlugin(): VideoPluginShape {
424
442
 
425
443
  // Encode the frame as image
426
444
  // The encoder will initialize from the first frame's properties
427
- const packet = await imageEncoder.encode(frame);
445
+ await imageEncoder.encode(frame);
446
+ const packet = await imageEncoder.receive();
428
447
  if (packet?.data) {
429
448
  // Convert Buffer to Uint8Array
430
449
  frameData = new Uint8Array(packet.data);