@zaadevofc/media-process 1.0.0

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.
@@ -0,0 +1,142 @@
1
+ type StickerShapeType = 'circle' | 'rounded' | 'oval' | 'default';
2
+ interface StickerMetadataType {
3
+ packageName?: string;
4
+ authorName?: string;
5
+ quality?: number;
6
+ shape?: StickerShapeType;
7
+ }
8
+
9
+ declare const FFMPEG_CONSTANTS: {
10
+ readonly OPUS: {
11
+ readonly CODEC: "libopus";
12
+ readonly CHANNELS: 1;
13
+ readonly FREQUENCY: 48000;
14
+ readonly BITRATE: "48k";
15
+ readonly FORMAT: "ogg";
16
+ };
17
+ readonly THUMBNAIL: {
18
+ readonly SIZE: 100;
19
+ readonly QUALITY: 50;
20
+ readonly TIMESTAMP: "10%";
21
+ };
22
+ readonly STICKER: {
23
+ readonly SIZE: 512;
24
+ readonly MAX_DURATION: 6;
25
+ readonly FPS: 10;
26
+ readonly DEFAULT_QUALITY: 60;
27
+ readonly COMPRESSION_LEVEL: 6;
28
+ };
29
+ readonly MIME: {
30
+ readonly AUDIO: "audio/";
31
+ readonly VIDEO: "video/";
32
+ readonly IMAGE: "image/";
33
+ readonly GIF: "image/gif";
34
+ readonly MP4: "video/mp4";
35
+ };
36
+ };
37
+ type MediaInput = string | ArrayBuffer | Buffer;
38
+ type FileExtension = 'wav' | 'ogg' | 'mp4' | 'gif' | 'jpg' | 'webp' | 'tmp' | 'mp3' | 'png';
39
+ interface FFmpegConfig {
40
+ input: string;
41
+ output: string;
42
+ options: string[];
43
+ onEnd: () => Promise<void>;
44
+ onError: (err: Error) => Promise<void>;
45
+ }
46
+ declare const initializeFFmpeg: (disable?: boolean) => Promise<void>;
47
+ declare class FileManager {
48
+ private static generateUniqueId;
49
+ static createTempPath(prefix: string, ext: FileExtension): string;
50
+ static cleanup(files: string[]): Promise<void>;
51
+ static safeReadFile(filePath: string): Promise<Buffer>;
52
+ static safeWriteFile(filePath: string, data: Buffer): Promise<void>;
53
+ }
54
+ declare class BufferConverter {
55
+ static toBuffer(input: MediaInput): Promise<Buffer>;
56
+ private static fromString;
57
+ private static fromUrl;
58
+ static getExtension(buffer: Buffer): Promise<FileExtension>;
59
+ }
60
+ declare class MimeValidator {
61
+ static validate(fileType: any, expectedPrefix: string): void;
62
+ static isMedia(mime: string): boolean;
63
+ static isAnimated(mime: string): boolean;
64
+ }
65
+ declare class FFmpegProcessor {
66
+ static process(config: FFmpegConfig): Promise<void>;
67
+ static getDuration(filePath: string): Promise<number>;
68
+ }
69
+
70
+ type AudioType = 'opus' | 'mp3';
71
+ declare class AudioProcessor {
72
+ static toOpus(input: MediaInput): Promise<Buffer>;
73
+ static toMp3(input: MediaInput): Promise<Buffer>;
74
+ static convert(input: MediaInput, type?: AudioType): Promise<Buffer>;
75
+ }
76
+
77
+ declare class VideoProcessor {
78
+ static toMp4(input: MediaInput): Promise<Buffer>;
79
+ static thumbnail(input: MediaInput): Promise<string>;
80
+ static duration(filePath: string): Promise<number>;
81
+ }
82
+
83
+ declare class ImageProcessor {
84
+ static thumbnail(buffer: Buffer): Promise<string>;
85
+ static resize(buffer: Buffer, width: number, height: number): Promise<Buffer>;
86
+ static toJpeg(input: MediaInput): Promise<Buffer>;
87
+ static resizeForSticker(buffer: Buffer, quality: number, shape?: string): Promise<Buffer>;
88
+ }
89
+
90
+ declare class StickerProcessor {
91
+ static create(input: MediaInput, metadata?: StickerMetadataType): Promise<Buffer>;
92
+ private static createExifMetadata;
93
+ private static processAnimated;
94
+ private static getExtension;
95
+ }
96
+
97
+ declare class DocumentProcessor {
98
+ static create(input: MediaInput): Promise<{
99
+ document: Buffer<ArrayBufferLike>;
100
+ mimetype: string;
101
+ ext: string;
102
+ fileName: string;
103
+ jpegThumbnail: string;
104
+ }>;
105
+ }
106
+
107
+ declare class Media {
108
+ private input;
109
+ constructor(input: MediaInput);
110
+ get audio(): {
111
+ toOpus: () => Promise<Buffer<ArrayBufferLike>>;
112
+ toMp3: () => Promise<Buffer<ArrayBufferLike>>;
113
+ convert: (type?: AudioType) => Promise<Buffer<ArrayBufferLike>>;
114
+ };
115
+ get video(): {
116
+ toMp4: () => Promise<Buffer<ArrayBufferLike>>;
117
+ thumbnail: () => Promise<string>;
118
+ };
119
+ get image(): {
120
+ toJpeg: () => Promise<Buffer<ArrayBufferLike>>;
121
+ thumbnail: () => Promise<string>;
122
+ resize: (width: number, height: number) => Promise<Buffer<ArrayBufferLike>>;
123
+ };
124
+ get sticker(): {
125
+ create: (metadata?: StickerMetadataType) => Promise<Buffer<ArrayBufferLike>>;
126
+ };
127
+ get document(): {
128
+ create: () => Promise<{
129
+ document: Buffer<ArrayBufferLike>;
130
+ mimetype: string;
131
+ ext: string;
132
+ fileName: string;
133
+ jpegThumbnail: string;
134
+ }>;
135
+ };
136
+ get thumbnail(): {
137
+ get: () => Promise<string>;
138
+ };
139
+ toBuffer(): Promise<Buffer>;
140
+ }
141
+
142
+ export { AudioProcessor, type AudioType, BufferConverter, DocumentProcessor, FFMPEG_CONSTANTS, type FFmpegConfig, FFmpegProcessor, type FileExtension, FileManager, ImageProcessor, Media, type MediaInput, MimeValidator, type StickerMetadataType, StickerProcessor, type StickerShapeType, VideoProcessor, initializeFFmpeg };
@@ -0,0 +1,142 @@
1
+ type StickerShapeType = 'circle' | 'rounded' | 'oval' | 'default';
2
+ interface StickerMetadataType {
3
+ packageName?: string;
4
+ authorName?: string;
5
+ quality?: number;
6
+ shape?: StickerShapeType;
7
+ }
8
+
9
+ declare const FFMPEG_CONSTANTS: {
10
+ readonly OPUS: {
11
+ readonly CODEC: "libopus";
12
+ readonly CHANNELS: 1;
13
+ readonly FREQUENCY: 48000;
14
+ readonly BITRATE: "48k";
15
+ readonly FORMAT: "ogg";
16
+ };
17
+ readonly THUMBNAIL: {
18
+ readonly SIZE: 100;
19
+ readonly QUALITY: 50;
20
+ readonly TIMESTAMP: "10%";
21
+ };
22
+ readonly STICKER: {
23
+ readonly SIZE: 512;
24
+ readonly MAX_DURATION: 6;
25
+ readonly FPS: 10;
26
+ readonly DEFAULT_QUALITY: 60;
27
+ readonly COMPRESSION_LEVEL: 6;
28
+ };
29
+ readonly MIME: {
30
+ readonly AUDIO: "audio/";
31
+ readonly VIDEO: "video/";
32
+ readonly IMAGE: "image/";
33
+ readonly GIF: "image/gif";
34
+ readonly MP4: "video/mp4";
35
+ };
36
+ };
37
+ type MediaInput = string | ArrayBuffer | Buffer;
38
+ type FileExtension = 'wav' | 'ogg' | 'mp4' | 'gif' | 'jpg' | 'webp' | 'tmp' | 'mp3' | 'png';
39
+ interface FFmpegConfig {
40
+ input: string;
41
+ output: string;
42
+ options: string[];
43
+ onEnd: () => Promise<void>;
44
+ onError: (err: Error) => Promise<void>;
45
+ }
46
+ declare const initializeFFmpeg: (disable?: boolean) => Promise<void>;
47
+ declare class FileManager {
48
+ private static generateUniqueId;
49
+ static createTempPath(prefix: string, ext: FileExtension): string;
50
+ static cleanup(files: string[]): Promise<void>;
51
+ static safeReadFile(filePath: string): Promise<Buffer>;
52
+ static safeWriteFile(filePath: string, data: Buffer): Promise<void>;
53
+ }
54
+ declare class BufferConverter {
55
+ static toBuffer(input: MediaInput): Promise<Buffer>;
56
+ private static fromString;
57
+ private static fromUrl;
58
+ static getExtension(buffer: Buffer): Promise<FileExtension>;
59
+ }
60
+ declare class MimeValidator {
61
+ static validate(fileType: any, expectedPrefix: string): void;
62
+ static isMedia(mime: string): boolean;
63
+ static isAnimated(mime: string): boolean;
64
+ }
65
+ declare class FFmpegProcessor {
66
+ static process(config: FFmpegConfig): Promise<void>;
67
+ static getDuration(filePath: string): Promise<number>;
68
+ }
69
+
70
+ type AudioType = 'opus' | 'mp3';
71
+ declare class AudioProcessor {
72
+ static toOpus(input: MediaInput): Promise<Buffer>;
73
+ static toMp3(input: MediaInput): Promise<Buffer>;
74
+ static convert(input: MediaInput, type?: AudioType): Promise<Buffer>;
75
+ }
76
+
77
+ declare class VideoProcessor {
78
+ static toMp4(input: MediaInput): Promise<Buffer>;
79
+ static thumbnail(input: MediaInput): Promise<string>;
80
+ static duration(filePath: string): Promise<number>;
81
+ }
82
+
83
+ declare class ImageProcessor {
84
+ static thumbnail(buffer: Buffer): Promise<string>;
85
+ static resize(buffer: Buffer, width: number, height: number): Promise<Buffer>;
86
+ static toJpeg(input: MediaInput): Promise<Buffer>;
87
+ static resizeForSticker(buffer: Buffer, quality: number, shape?: string): Promise<Buffer>;
88
+ }
89
+
90
+ declare class StickerProcessor {
91
+ static create(input: MediaInput, metadata?: StickerMetadataType): Promise<Buffer>;
92
+ private static createExifMetadata;
93
+ private static processAnimated;
94
+ private static getExtension;
95
+ }
96
+
97
+ declare class DocumentProcessor {
98
+ static create(input: MediaInput): Promise<{
99
+ document: Buffer<ArrayBufferLike>;
100
+ mimetype: string;
101
+ ext: string;
102
+ fileName: string;
103
+ jpegThumbnail: string;
104
+ }>;
105
+ }
106
+
107
+ declare class Media {
108
+ private input;
109
+ constructor(input: MediaInput);
110
+ get audio(): {
111
+ toOpus: () => Promise<Buffer<ArrayBufferLike>>;
112
+ toMp3: () => Promise<Buffer<ArrayBufferLike>>;
113
+ convert: (type?: AudioType) => Promise<Buffer<ArrayBufferLike>>;
114
+ };
115
+ get video(): {
116
+ toMp4: () => Promise<Buffer<ArrayBufferLike>>;
117
+ thumbnail: () => Promise<string>;
118
+ };
119
+ get image(): {
120
+ toJpeg: () => Promise<Buffer<ArrayBufferLike>>;
121
+ thumbnail: () => Promise<string>;
122
+ resize: (width: number, height: number) => Promise<Buffer<ArrayBufferLike>>;
123
+ };
124
+ get sticker(): {
125
+ create: (metadata?: StickerMetadataType) => Promise<Buffer<ArrayBufferLike>>;
126
+ };
127
+ get document(): {
128
+ create: () => Promise<{
129
+ document: Buffer<ArrayBufferLike>;
130
+ mimetype: string;
131
+ ext: string;
132
+ fileName: string;
133
+ jpegThumbnail: string;
134
+ }>;
135
+ };
136
+ get thumbnail(): {
137
+ get: () => Promise<string>;
138
+ };
139
+ toBuffer(): Promise<Buffer>;
140
+ }
141
+
142
+ export { AudioProcessor, type AudioType, BufferConverter, DocumentProcessor, FFMPEG_CONSTANTS, type FFmpegConfig, FFmpegProcessor, type FileExtension, FileManager, ImageProcessor, Media, type MediaInput, MimeValidator, type StickerMetadataType, StickerProcessor, type StickerShapeType, VideoProcessor, initializeFFmpeg };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';var S=require('fluent-ffmpeg'),F=require('fs/promises'),os=require('os'),C=require('path'),fileType=require('file-type'),M=require('sharp'),H=require('node-webpmux');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var S__default=/*#__PURE__*/_interopDefault(S);var F__default=/*#__PURE__*/_interopDefault(F);var C__default=/*#__PURE__*/_interopDefault(C);var M__default=/*#__PURE__*/_interopDefault(M);var H__default=/*#__PURE__*/_interopDefault(H);var o={OPUS:{CODEC:"libopus",CHANNELS:1,FREQUENCY:48e3,BITRATE:"48k",FORMAT:"ogg"},THUMBNAIL:{SIZE:100,QUALITY:50,TIMESTAMP:"10%"},STICKER:{SIZE:512,MAX_DURATION:6,FPS:10,DEFAULT_QUALITY:60,COMPRESSION_LEVEL:6},MIME:{AUDIO:"audio/",VIDEO:"video/",IMAGE:"image/",GIF:"image/gif",MP4:"video/mp4"}},X=async(d=false)=>{if(!d)try{let t=(await import('@ffmpeg-installer/ffmpeg')).default,e=(await import('@ffprobe-installer/ffprobe')).default;S__default.default.setFfmpegPath(t.path),S__default.default.setFfprobePath(e.path);}catch{}},s=class{static generateUniqueId(){return `${Date.now()}_${Math.random().toString(36).slice(2,11)}`}static createTempPath(t,e){return C__default.default.join(os.tmpdir(),`${t}_${this.generateUniqueId()}.${e}`)}static async cleanup(t){await Promise.allSettled(t.map(e=>F__default.default.unlink(e)));}static async safeReadFile(t){try{return await F__default.default.readFile(t)}catch{throw new Error(`Failed to read file: ${t}`)}}static async safeWriteFile(t,e){try{await F__default.default.writeFile(t,e);}catch{throw new Error(`Failed to write file: ${t}`)}}},c=class{static async toBuffer(t){if(Buffer.isBuffer(t))return t;if(t instanceof ArrayBuffer)return Buffer.from(t);if(typeof t=="string")return this.fromString(t);throw new Error("Invalid input type: expected string, Buffer, or ArrayBuffer")}static async fromString(t){if(t.startsWith("http://")||t.startsWith("https://"))return this.fromUrl(t);try{if(await F__default.default.stat(t).then(e=>e.isFile()).catch(()=>!1))return await F__default.default.readFile(t)}catch{}return Buffer.from(t,"base64")}static async fromUrl(t){try{let e=await fetch(t);if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);let a=await e.arrayBuffer();return Buffer.from(a)}catch(e){throw new Error(`Failed to fetch URL: ${e.message}`)}}static async getExtension(t){let{fileTypeFromBuffer:e}=await import('file-type'),a=await e(t);if(!a)return "tmp";let r=a.ext.toLowerCase();return ["wav","ogg","mp4","gif","jpg","webp","tmp","mp3","png"].includes(r)?r:"tmp"}},g=class{static validate(t,e){if(!t?.mime?.startsWith(e))throw new Error(`Invalid file type: expected ${e}*, got ${t?.mime||"unknown"}`)}static isMedia(t){return t.startsWith(o.MIME.IMAGE)||t.startsWith(o.MIME.VIDEO)}static isAnimated(t){return t===o.MIME.GIF||t.startsWith(o.MIME.VIDEO)}},I=class{static async process(t){return new Promise((e,a)=>{let r=S__default.default(t.input).output(t.output);for(let i=0;i<t.options.length;i++){let n=t.options[i];n.startsWith("-")&&i+1<t.options.length&&!t.options[i+1].startsWith("-")?(r.outputOptions(n,t.options[i+1]),i++):r.outputOptions(n);}r.on("end",async()=>{try{await t.onEnd(),e();}catch(i){a(i);}}).on("error",async i=>{try{await t.onError(i);}finally{a(i);}}).run();})}static async getDuration(t){return new Promise((e,a)=>{S__default.default.ffprobe(t,(r,i)=>{if(r)return a(r);e(i.format.duration||0);});})}};var B=class{static async toOpus(t){return this.convert(t,"opus")}static async toMp3(t){return this.convert(t,"mp3")}static async convert(t,e="opus"){let a=await c.toBuffer(t),r=await fileType.fileTypeFromBuffer(a);g.validate(r,o.MIME.AUDIO);let i=await c.getExtension(a),n=s.createTempPath("audio_in",i),l=e==="opus"?"ogg":"mp3",f=s.createTempPath("audio_out",l);await s.safeWriteFile(n,a);let u;try{let m=e==="opus"?["-vn","-c:a","libopus","-b:a","48k","-ac","1","-avoid_negative_ts","make_zero","-map_metadata","-1","-f","ogg"]:["-vn","-c:a","libmp3lame","-b:a","128k","-ac","2","-avoid_negative_ts","make_zero","-map_metadata","-1","-f","mp3"];return await I.process({input:n,output:f,options:m,onEnd:async()=>{u=await s.safeReadFile(f);},onError:async()=>{await s.cleanup([n,f]);}}),await s.cleanup([n,f]),u}catch(m){throw await s.cleanup([n,f]),new Error(`${e.toUpperCase()} conversion failed: ${m.message}`)}}};var E=class{static async toMp4(t){let e=await c.toBuffer(t),a=await fileType.fileTypeFromBuffer(e);g.validate(a,o.MIME.VIDEO);let r=await c.getExtension(e),i=s.createTempPath("video_in",r),n=s.createTempPath("video_out","mp4");await s.safeWriteFile(i,e);let l;try{let f=["-c:v","libx264","-preset","ultrafast","-crf","28","-c:a","aac","-b:a","128k","-movflags","+faststart","-vf","scale=trunc(iw/2)*2:trunc(ih/2)*2","-pix_fmt","yuv420p","-f","mp4"];return await I.process({input:i,output:n,options:f,onEnd:async()=>{l=await s.safeReadFile(n);},onError:async()=>{await s.cleanup([i,n]);}}),await s.cleanup([i,n]),l}catch(f){throw await s.cleanup([i,n]),new Error(`Video re-encoding failed: ${f.message}`)}}static async thumbnail(t){let e=await c.toBuffer(t),a=await fileType.fileTypeFromBuffer(e);g.validate(a,o.MIME.VIDEO);let r=await c.getExtension(e),i=s.createTempPath("video",r),n=s.createTempPath("thumb","jpg");await s.safeWriteFile(i,e);let l;try{return await new Promise((f,u)=>{S__default.default(i).screenshots({timestamps:[o.THUMBNAIL.TIMESTAMP],filename:C__default.default.basename(n),folder:C__default.default.dirname(n),size:`${o.THUMBNAIL.SIZE}x${o.THUMBNAIL.SIZE}`}).on("end",async()=>{try{l=(await s.safeReadFile(n)).toString("base64"),f();}catch(m){u(m);}}).on("error",u);}),await s.cleanup([i,n]),l}catch(f){throw await s.cleanup([i,n]),new Error(`Thumbnail generation failed: ${f.message}`)}}static async duration(t){return I.getDuration(t)}};var h=class{static async thumbnail(t){let e=M__default.default(t).resize(o.THUMBNAIL.SIZE,o.THUMBNAIL.SIZE,{fit:"cover"});return (await e.metadata()).format==="gif"&&(e=M__default.default(t,{pages:1}).resize(o.THUMBNAIL.SIZE,o.THUMBNAIL.SIZE)),(await e.jpeg({quality:o.THUMBNAIL.QUALITY}).toBuffer()).toString("base64")}static async resize(t,e,a){return M__default.default(t).resize(e,a,{fit:"cover"}).png().toBuffer()}static async toJpeg(t){let e=await c.toBuffer(t),a=await M__default.default(e).metadata();if(!a.format)throw new Error("Invalid image type: format could not be determined");return a.format==="webp"||a.format==="png"?M__default.default(e).jpeg().toBuffer():e}static async resizeForSticker(t,e,a="default"){let r=o.STICKER.SIZE,i=M__default.default(t).resize(r,r,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}});if(a!=="default"){let n=Buffer.alloc(r*r);for(let f=0;f<r;f++)for(let u=0;u<r;u++){let m=false,y=r/2,T=r/2;switch(a){case "circle":{let p=u-y,x=f-T;m=p*p+x*x<=y*y;break}case "rounded":{let p=r/10,x=u>=p&&u<r-p&&f>=0&&f<r,v=u>=0&&u<r&&f>=p&&f<r-p,b=false,L=[[p,p],[r-p,p],[p,r-p],[r-p,r-p]];for(let[R,D]of L){let N=u-R,O=f-D;if(N*N+O*O<=p*p){b=true;break}}m=x||v||b;break}case "oval":{let p=r/2,x=r/3,v=(u-y)/p,b=(f-T)/x;m=v*v+b*b<=1;break}}n[f*r+u]=m?255:0;}let l=M__default.default(n,{raw:{width:r,height:r,channels:1}});i=i.joinChannel(await l.toBuffer());}return i.webp({quality:e}).toBuffer()}};var P=()=>Date.now().toString(36)+Math.random().toString(36).slice(2,8),U=d=>d;var A=class{static async create(t,e){try{let a=await c.toBuffer(t),r=await fileType.fileTypeFromBuffer(a);if(!r)throw new Error("Unable to detect file type");let i=e?.quality||o.STICKER.DEFAULT_QUALITY,n=g.isAnimated(r.mime),l=e?.shape||"default",f=n?await this.processAnimated(a,r.mime,i):await h.resizeForSticker(a,i,l),u=this.createExifMetadata(e),m=new H__default.default.Image;await m.load(f),m.exif=u;let y=await m.save(null);return Buffer.isBuffer(y)?y:Buffer.from(y)}catch(a){throw new Error(`Sticker creation failed: ${a.message||a}`)}}static createExifMetadata(t){let e={"sticker-pack-id":P(),"sticker-pack-name":t?.packageName||"Zaileys Library","sticker-pack-publisher":t?.authorName||"https://github.com/zeative/zaileys",emojis:["\u{1F913}"],"android-app-store-link":"https://play.google.com/store/apps/details?id=com.marsvard.stickermakerforwhatsapp","ios-app-store-link":"https://itunes.apple.com/app/sticker-maker-studio/id1443326857"},a=Buffer.from([73,73,42,0,8,0,0,0,1,0,65,87,7,0,0,0,0,0,22,0,0,0]),r=Buffer.from(JSON.stringify(e),"utf8"),i=Buffer.concat([a,r]);return i.writeUIntLE(r.length,14,4),i}static async processAnimated(t,e,a){let r=this.getExtension(e),i=s.createTempPath("sticker_in",r),n=s.createTempPath("sticker_out","webp");await s.safeWriteFile(i,t);let l=o.STICKER.MAX_DURATION;try{let p=await E.duration(i);l=U(Math.min(p,o.STICKER.MAX_DURATION));}catch{console.warn("Using default duration:",o.STICKER.MAX_DURATION);}let f=o.STICKER.SIZE,u=o.STICKER.FPS,m=`scale=${f}:${f}:force_original_aspect_ratio=decrease,fps=${u},pad=${f}:${f}:(ow-iw)/2:(oh-ih)/2:color=0x00000000,format=rgba`,y=Math.max(1,Math.min(100,a)),T;try{return await I.process({input:i,output:n,options:["-vcodec libwebp",`-vf ${m}`,`-q:v ${y}`,"-loop 0","-preset default","-an","-vsync 0",`-t ${l}`,`-compression_level ${o.STICKER.COMPRESSION_LEVEL}`],onEnd:async()=>{T=await s.safeReadFile(n);},onError:async()=>{await s.cleanup([i,n]);}}),await s.cleanup([i,n]),T}catch(p){throw await s.cleanup([i,n]),new Error(`Animated sticker processing failed: ${p.message}`)}}static getExtension(t){return t===o.MIME.GIF?"gif":t.startsWith(o.MIME.VIDEO)?"mp4":"tmp"}};var k=class{static async create(t){try{let e=await c.toBuffer(t),a=await fileType.fileTypeFromBuffer(e);if(!a)throw new Error("Unable to detect file type");let r;return a.mime.startsWith(o.MIME.VIDEO)?r=await E.thumbnail(e):g.isMedia(a.mime)?r=await h.thumbnail(e):r="",{document:e,mimetype:a.mime,ext:a.ext,fileName:P(),jpegThumbnail:r}}catch(e){throw new Error(`Document creation failed: ${e.message||e}`)}}};var $=class{input;constructor(t){this.input=t;}get audio(){return {toOpus:()=>B.toOpus(this.input),toMp3:()=>B.toMp3(this.input),convert:(t="opus")=>B.convert(this.input,t)}}get video(){return {toMp4:()=>E.toMp4(this.input),thumbnail:()=>E.thumbnail(this.input)}}get image(){return {toJpeg:()=>h.toJpeg(this.input),thumbnail:async()=>{let t=await c.toBuffer(this.input);return h.thumbnail(t)},resize:async(t,e)=>{let a=await c.toBuffer(this.input);return h.resize(a,t,e)}}}get sticker(){return {create:t=>A.create(this.input,t)}}get document(){return {create:()=>k.create(this.input)}}get thumbnail(){return {get:async()=>{let t=await c.toBuffer(this.input),e=await fileType.fileTypeFromBuffer(t);if(!e||!g.isMedia(e.mime))throw new Error("Invalid media type: expected image or video");return e.mime.startsWith(o.MIME.VIDEO)?E.thumbnail(this.input):h.thumbnail(t)}}}async toBuffer(){return c.toBuffer(this.input)}};
2
+ exports.AudioProcessor=B;exports.BufferConverter=c;exports.DocumentProcessor=k;exports.FFMPEG_CONSTANTS=o;exports.FFmpegProcessor=I;exports.FileManager=s;exports.ImageProcessor=h;exports.Media=$;exports.MimeValidator=g;exports.StickerProcessor=A;exports.VideoProcessor=E;exports.initializeFFmpeg=X;
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import P from'fluent-ffmpeg';import v from'fs/promises';import {tmpdir}from'os';import U from'path';import {fileTypeFromBuffer}from'file-type';import x from'sharp';import Z from'node-webpmux';var o={OPUS:{CODEC:"libopus",CHANNELS:1,FREQUENCY:48e3,BITRATE:"48k",FORMAT:"ogg"},THUMBNAIL:{SIZE:100,QUALITY:50,TIMESTAMP:"10%"},STICKER:{SIZE:512,MAX_DURATION:6,FPS:10,DEFAULT_QUALITY:60,COMPRESSION_LEVEL:6},MIME:{AUDIO:"audio/",VIDEO:"video/",IMAGE:"image/",GIF:"image/gif",MP4:"video/mp4"}},tt=async(d=false)=>{if(!d)try{let t=(await import('@ffmpeg-installer/ffmpeg')).default,e=(await import('@ffprobe-installer/ffprobe')).default;P.setFfmpegPath(t.path),P.setFfprobePath(e.path);}catch{}},s=class{static generateUniqueId(){return `${Date.now()}_${Math.random().toString(36).slice(2,11)}`}static createTempPath(t,e){return U.join(tmpdir(),`${t}_${this.generateUniqueId()}.${e}`)}static async cleanup(t){await Promise.allSettled(t.map(e=>v.unlink(e)));}static async safeReadFile(t){try{return await v.readFile(t)}catch{throw new Error(`Failed to read file: ${t}`)}}static async safeWriteFile(t,e){try{await v.writeFile(t,e);}catch{throw new Error(`Failed to write file: ${t}`)}}},c=class{static async toBuffer(t){if(Buffer.isBuffer(t))return t;if(t instanceof ArrayBuffer)return Buffer.from(t);if(typeof t=="string")return this.fromString(t);throw new Error("Invalid input type: expected string, Buffer, or ArrayBuffer")}static async fromString(t){if(t.startsWith("http://")||t.startsWith("https://"))return this.fromUrl(t);try{if(await v.stat(t).then(e=>e.isFile()).catch(()=>!1))return await v.readFile(t)}catch{}return Buffer.from(t,"base64")}static async fromUrl(t){try{let e=await fetch(t);if(!e.ok)throw new Error(`HTTP ${e.status}: ${e.statusText}`);let a=await e.arrayBuffer();return Buffer.from(a)}catch(e){throw new Error(`Failed to fetch URL: ${e.message}`)}}static async getExtension(t){let{fileTypeFromBuffer:e}=await import('file-type'),a=await e(t);if(!a)return "tmp";let r=a.ext.toLowerCase();return ["wav","ogg","mp4","gif","jpg","webp","tmp","mp3","png"].includes(r)?r:"tmp"}},g=class{static validate(t,e){if(!t?.mime?.startsWith(e))throw new Error(`Invalid file type: expected ${e}*, got ${t?.mime||"unknown"}`)}static isMedia(t){return t.startsWith(o.MIME.IMAGE)||t.startsWith(o.MIME.VIDEO)}static isAnimated(t){return t===o.MIME.GIF||t.startsWith(o.MIME.VIDEO)}},M=class{static async process(t){return new Promise((e,a)=>{let r=P(t.input).output(t.output);for(let i=0;i<t.options.length;i++){let n=t.options[i];n.startsWith("-")&&i+1<t.options.length&&!t.options[i+1].startsWith("-")?(r.outputOptions(n,t.options[i+1]),i++):r.outputOptions(n);}r.on("end",async()=>{try{await t.onEnd(),e();}catch(i){a(i);}}).on("error",async i=>{try{await t.onError(i);}finally{a(i);}}).run();})}static async getDuration(t){return new Promise((e,a)=>{P.ffprobe(t,(r,i)=>{if(r)return a(r);e(i.format.duration||0);});})}};var T=class{static async toOpus(t){return this.convert(t,"opus")}static async toMp3(t){return this.convert(t,"mp3")}static async convert(t,e="opus"){let a=await c.toBuffer(t),r=await fileTypeFromBuffer(a);g.validate(r,o.MIME.AUDIO);let i=await c.getExtension(a),n=s.createTempPath("audio_in",i),l=e==="opus"?"ogg":"mp3",f=s.createTempPath("audio_out",l);await s.safeWriteFile(n,a);let u;try{let m=e==="opus"?["-vn","-c:a","libopus","-b:a","48k","-ac","1","-avoid_negative_ts","make_zero","-map_metadata","-1","-f","ogg"]:["-vn","-c:a","libmp3lame","-b:a","128k","-ac","2","-avoid_negative_ts","make_zero","-map_metadata","-1","-f","mp3"];return await M.process({input:n,output:f,options:m,onEnd:async()=>{u=await s.safeReadFile(f);},onError:async()=>{await s.cleanup([n,f]);}}),await s.cleanup([n,f]),u}catch(m){throw await s.cleanup([n,f]),new Error(`${e.toUpperCase()} conversion failed: ${m.message}`)}}};var I=class{static async toMp4(t){let e=await c.toBuffer(t),a=await fileTypeFromBuffer(e);g.validate(a,o.MIME.VIDEO);let r=await c.getExtension(e),i=s.createTempPath("video_in",r),n=s.createTempPath("video_out","mp4");await s.safeWriteFile(i,e);let l;try{let f=["-c:v","libx264","-preset","ultrafast","-crf","28","-c:a","aac","-b:a","128k","-movflags","+faststart","-vf","scale=trunc(iw/2)*2:trunc(ih/2)*2","-pix_fmt","yuv420p","-f","mp4"];return await M.process({input:i,output:n,options:f,onEnd:async()=>{l=await s.safeReadFile(n);},onError:async()=>{await s.cleanup([i,n]);}}),await s.cleanup([i,n]),l}catch(f){throw await s.cleanup([i,n]),new Error(`Video re-encoding failed: ${f.message}`)}}static async thumbnail(t){let e=await c.toBuffer(t),a=await fileTypeFromBuffer(e);g.validate(a,o.MIME.VIDEO);let r=await c.getExtension(e),i=s.createTempPath("video",r),n=s.createTempPath("thumb","jpg");await s.safeWriteFile(i,e);let l;try{return await new Promise((f,u)=>{P(i).screenshots({timestamps:[o.THUMBNAIL.TIMESTAMP],filename:U.basename(n),folder:U.dirname(n),size:`${o.THUMBNAIL.SIZE}x${o.THUMBNAIL.SIZE}`}).on("end",async()=>{try{l=(await s.safeReadFile(n)).toString("base64"),f();}catch(m){u(m);}}).on("error",u);}),await s.cleanup([i,n]),l}catch(f){throw await s.cleanup([i,n]),new Error(`Thumbnail generation failed: ${f.message}`)}}static async duration(t){return M.getDuration(t)}};var h=class{static async thumbnail(t){let e=x(t).resize(o.THUMBNAIL.SIZE,o.THUMBNAIL.SIZE,{fit:"cover"});return (await e.metadata()).format==="gif"&&(e=x(t,{pages:1}).resize(o.THUMBNAIL.SIZE,o.THUMBNAIL.SIZE)),(await e.jpeg({quality:o.THUMBNAIL.QUALITY}).toBuffer()).toString("base64")}static async resize(t,e,a){return x(t).resize(e,a,{fit:"cover"}).png().toBuffer()}static async toJpeg(t){let e=await c.toBuffer(t),a=await x(e).metadata();if(!a.format)throw new Error("Invalid image type: format could not be determined");return a.format==="webp"||a.format==="png"?x(e).jpeg().toBuffer():e}static async resizeForSticker(t,e,a="default"){let r=o.STICKER.SIZE,i=x(t).resize(r,r,{fit:"contain",background:{r:0,g:0,b:0,alpha:0}});if(a!=="default"){let n=Buffer.alloc(r*r);for(let f=0;f<r;f++)for(let u=0;u<r;u++){let m=false,y=r/2,b=r/2;switch(a){case "circle":{let p=u-y,B=f-b;m=p*p+B*B<=y*y;break}case "rounded":{let p=r/10,B=u>=p&&u<r-p&&f>=0&&f<r,S=u>=0&&u<r&&f>=p&&f<r-p,F=false,R=[[p,p],[r-p,p],[p,r-p],[r-p,r-p]];for(let[D,V]of R){let O=u-D,_=f-V;if(O*O+_*_<=p*p){F=true;break}}m=B||S||F;break}case "oval":{let p=r/2,B=r/3,S=(u-y)/p,F=(f-b)/B;m=S*S+F*F<=1;break}}n[f*r+u]=m?255:0;}let l=x(n,{raw:{width:r,height:r,channels:1}});i=i.joinChannel(await l.toBuffer());}return i.webp({quality:e}).toBuffer()}};var A=()=>Date.now().toString(36)+Math.random().toString(36).slice(2,8),$=d=>d;var k=class{static async create(t,e){try{let a=await c.toBuffer(t),r=await fileTypeFromBuffer(a);if(!r)throw new Error("Unable to detect file type");let i=e?.quality||o.STICKER.DEFAULT_QUALITY,n=g.isAnimated(r.mime),l=e?.shape||"default",f=n?await this.processAnimated(a,r.mime,i):await h.resizeForSticker(a,i,l),u=this.createExifMetadata(e),m=new Z.Image;await m.load(f),m.exif=u;let y=await m.save(null);return Buffer.isBuffer(y)?y:Buffer.from(y)}catch(a){throw new Error(`Sticker creation failed: ${a.message||a}`)}}static createExifMetadata(t){let e={"sticker-pack-id":A(),"sticker-pack-name":t?.packageName||"Zaileys Library","sticker-pack-publisher":t?.authorName||"https://github.com/zeative/zaileys",emojis:["\u{1F913}"],"android-app-store-link":"https://play.google.com/store/apps/details?id=com.marsvard.stickermakerforwhatsapp","ios-app-store-link":"https://itunes.apple.com/app/sticker-maker-studio/id1443326857"},a=Buffer.from([73,73,42,0,8,0,0,0,1,0,65,87,7,0,0,0,0,0,22,0,0,0]),r=Buffer.from(JSON.stringify(e),"utf8"),i=Buffer.concat([a,r]);return i.writeUIntLE(r.length,14,4),i}static async processAnimated(t,e,a){let r=this.getExtension(e),i=s.createTempPath("sticker_in",r),n=s.createTempPath("sticker_out","webp");await s.safeWriteFile(i,t);let l=o.STICKER.MAX_DURATION;try{let p=await I.duration(i);l=$(Math.min(p,o.STICKER.MAX_DURATION));}catch{console.warn("Using default duration:",o.STICKER.MAX_DURATION);}let f=o.STICKER.SIZE,u=o.STICKER.FPS,m=`scale=${f}:${f}:force_original_aspect_ratio=decrease,fps=${u},pad=${f}:${f}:(ow-iw)/2:(oh-ih)/2:color=0x00000000,format=rgba`,y=Math.max(1,Math.min(100,a)),b;try{return await M.process({input:i,output:n,options:["-vcodec libwebp",`-vf ${m}`,`-q:v ${y}`,"-loop 0","-preset default","-an","-vsync 0",`-t ${l}`,`-compression_level ${o.STICKER.COMPRESSION_LEVEL}`],onEnd:async()=>{b=await s.safeReadFile(n);},onError:async()=>{await s.cleanup([i,n]);}}),await s.cleanup([i,n]),b}catch(p){throw await s.cleanup([i,n]),new Error(`Animated sticker processing failed: ${p.message}`)}}static getExtension(t){return t===o.MIME.GIF?"gif":t.startsWith(o.MIME.VIDEO)?"mp4":"tmp"}};var N=class{static async create(t){try{let e=await c.toBuffer(t),a=await fileTypeFromBuffer(e);if(!a)throw new Error("Unable to detect file type");let r;return a.mime.startsWith(o.MIME.VIDEO)?r=await I.thumbnail(e):g.isMedia(a.mime)?r=await h.thumbnail(e):r="",{document:e,mimetype:a.mime,ext:a.ext,fileName:A(),jpegThumbnail:r}}catch(e){throw new Error(`Document creation failed: ${e.message||e}`)}}};var L=class{input;constructor(t){this.input=t;}get audio(){return {toOpus:()=>T.toOpus(this.input),toMp3:()=>T.toMp3(this.input),convert:(t="opus")=>T.convert(this.input,t)}}get video(){return {toMp4:()=>I.toMp4(this.input),thumbnail:()=>I.thumbnail(this.input)}}get image(){return {toJpeg:()=>h.toJpeg(this.input),thumbnail:async()=>{let t=await c.toBuffer(this.input);return h.thumbnail(t)},resize:async(t,e)=>{let a=await c.toBuffer(this.input);return h.resize(a,t,e)}}}get sticker(){return {create:t=>k.create(this.input,t)}}get document(){return {create:()=>N.create(this.input)}}get thumbnail(){return {get:async()=>{let t=await c.toBuffer(this.input),e=await fileTypeFromBuffer(t);if(!e||!g.isMedia(e.mime))throw new Error("Invalid media type: expected image or video");return e.mime.startsWith(o.MIME.VIDEO)?I.thumbnail(this.input):h.thumbnail(t)}}}async toBuffer(){return c.toBuffer(this.input)}};
2
+ export{T as AudioProcessor,c as BufferConverter,N as DocumentProcessor,o as FFMPEG_CONSTANTS,M as FFmpegProcessor,s as FileManager,h as ImageProcessor,L as Media,g as MimeValidator,k as StickerProcessor,I as VideoProcessor,tt as initializeFFmpeg};
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@zaadevofc/media-process",
3
+ "version": "1.0.0",
4
+ "description": "WhatsApp Media Processing library for Zaileys",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.js"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsup"
18
+ },
19
+ "dependencies": {
20
+ "@ffmpeg-installer/ffmpeg": "^1.1.0",
21
+ "@ffprobe-installer/ffprobe": "^2.1.2",
22
+ "file-type": "^21.1.1",
23
+ "fluent-ffmpeg": "^2.1.3",
24
+ "node-webpmux": "^3.2.1",
25
+ "sharp": "0.33.0"
26
+ },
27
+ "devDependencies": {
28
+ "@types/fluent-ffmpeg": "^2.1.28",
29
+ "tsup": "^8.5.1",
30
+ "typescript": "^5.9.3"
31
+ }
32
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "declaration": true,
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "outDir": "./dist",
12
+ "rootDir": "./src"
13
+ },
14
+ "include": ["src/**/*"]
15
+ }