@shibam/sticker-maker 1.1.11 → 1.1.13

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.
package/dist/index.js CHANGED
@@ -42,6 +42,7 @@ class Sticker {
42
42
  this.metaInfo.category = this.metaInfo.category ?? [];
43
43
  this.metaInfo.type = this.metaInfo.type ?? StickerTypes.DEFAULT;
44
44
  this.metaInfo.quality = this.metaInfo?.quality ?? this.utils.getQuality(this.buffer);
45
+ this.metaInfo.text = this.metaInfo.text ?? '';
45
46
  }
46
47
  catch (error) {
47
48
  throw new Error(`Initialization error: ${error}`);
@@ -1,6 +1,8 @@
1
1
  import sharp from 'sharp';
2
2
  import { StickerTypes } from '../types/StickerTypes.js';
3
3
  import toGif from './toGif.js';
4
+ import TextOnImg from './textOnImg.js';
5
+ const textOnImg = new TextOnImg();
4
6
  /**
5
7
  * Converts a given buffer to WebP format with optional transformations.
6
8
  *
@@ -15,8 +17,8 @@ const ToWebp = async (buffer, metaInfo, mimeExt, mimeType) => {
15
17
  if (mimeExt === 'webp')
16
18
  return buffer;
17
19
  let data = mimeType?.includes('video')
18
- ? await toGif(buffer, mimeExt, metaInfo.type || StickerTypes.DEFAULT)
19
- : buffer;
20
+ ? await toGif(buffer, mimeExt, metaInfo.type || StickerTypes.DEFAULT, metaInfo.text ?? '')
21
+ : (metaInfo.text ? await textOnImg.drawText(buffer, metaInfo.text) : buffer);
20
22
  let isAnimated = mimeType?.includes('video') || mimeExt?.includes('gif');
21
23
  const res = sharp(data, { animated: isAnimated });
22
24
  if (metaInfo.type === StickerTypes.CIRCLE) {
@@ -0,0 +1,24 @@
1
+ import TextOnGif from 'text-on-gif';
2
+ const textOnGif = async (fileName, text) => {
3
+ return new Promise(async (resolve, reject) => {
4
+ try {
5
+ const gif = new TextOnGif({
6
+ file_path: fileName,
7
+ font_size: "18px",
8
+ font_color: "white",
9
+ font_family: "Arial",
10
+ stroke_color: "black",
11
+ stroke_width: 3,
12
+ });
13
+ const buff = await gif.textOnGif({
14
+ text,
15
+ get_as_buffer: true,
16
+ });
17
+ resolve(buff);
18
+ }
19
+ catch (error) {
20
+ reject(error);
21
+ }
22
+ });
23
+ };
24
+ export default textOnGif;
@@ -0,0 +1,54 @@
1
+ import { createCanvas, loadImage } from 'canvas';
2
+ export default class TextOnImage {
3
+ fontSize;
4
+ maxCharsPerLine;
5
+ constructor(maxCharsPerLine = 33) {
6
+ this.fontSize = 65;
7
+ this.maxCharsPerLine = maxCharsPerLine;
8
+ }
9
+ wrapText(ctx, text) {
10
+ const words = text.split(' ');
11
+ let lines = [];
12
+ let currentLine = words[0];
13
+ for (let i = 1; i < words.length; i++) {
14
+ const word = words[i];
15
+ const testLine = `${currentLine} ${word}`;
16
+ if (testLine.length > this.maxCharsPerLine) {
17
+ lines.push(currentLine);
18
+ currentLine = word;
19
+ }
20
+ else {
21
+ currentLine = testLine;
22
+ }
23
+ }
24
+ lines.push(currentLine);
25
+ return lines;
26
+ }
27
+ async drawText(imageBuffer, text, padding = { x: 10, y: 10 }) {
28
+ const image = await loadImage(imageBuffer);
29
+ const canvas = createCanvas(image.width, image.height);
30
+ const ctx = canvas.getContext('2d');
31
+ ctx.drawImage(image, 0, 0);
32
+ ctx.font = `bold ${this.fontSize}px Arial`;
33
+ ctx.fillStyle = 'white';
34
+ ctx.textAlign = 'center';
35
+ ctx.textBaseline = 'top';
36
+ ctx.lineWidth = 4;
37
+ ctx.strokeStyle = 'black';
38
+ const lines = this.wrapText(ctx, text);
39
+ const lineHeight = this.fontSize * 1.2;
40
+ const totalTextHeight = lines.length * lineHeight;
41
+ if (totalTextHeight + (2 * padding.y) > canvas.height) {
42
+ this.fontSize = (canvas.height - (2 * padding.y)) / (lines.length * 1.2);
43
+ ctx.font = `bold ${this.fontSize}px Arial`;
44
+ }
45
+ let y = canvas.height - totalTextHeight - padding.y;
46
+ lines.forEach(line => {
47
+ let x = canvas.width / 2;
48
+ ctx.strokeText(line, x, y);
49
+ ctx.fillText(line, x, y);
50
+ y += lineHeight;
51
+ });
52
+ return canvas.toBuffer('image/png');
53
+ }
54
+ }
package/dist/lib/toGif.js CHANGED
@@ -1,59 +1,41 @@
1
1
  import ffmpeg from 'fluent-ffmpeg';
2
2
  import ffmpegInstaller from '@ffmpeg-installer/ffmpeg';
3
- import { PassThrough } from 'stream';
3
+ import { tmpdir } from 'os';
4
+ import { writeFile, readFile, unlink } from 'fs/promises';
5
+ import { join } from 'path';
4
6
  import { StickerTypes } from '../types/StickerTypes.js';
7
+ import TextOnGif from './textOnGif.js';
5
8
  ffmpeg.setFfmpegPath(ffmpegInstaller.path);
6
- const videoToGif = (buffer, extType, type, retries = 3) => {
9
+ const videoToGif = (buffer, extType, type, text = '') => {
7
10
  return new Promise(async (resolve, reject) => {
8
11
  const execute = async (attempt) => {
9
- const outputStream = new PassThrough({ allowHalfOpen: false });
10
- const inputStream = new PassThrough({ allowHalfOpen: false });
12
+ const filename = join(tmpdir(), `${Math.random().toString(36)}.${extType}`);
13
+ const outputFilename = join(tmpdir(), `${Math.random().toString(36)}.gif`);
14
+ const retries = 3;
11
15
  try {
12
- const chunks = [];
13
- inputStream.write(buffer);
14
- inputStream.end();
15
- outputStream.on('data', (chunk) => {
16
- chunks.push(chunk);
17
- });
18
- outputStream.on('end', () => {
19
- resolve(Buffer.concat(chunks));
20
- inputStream.destroy();
21
- outputStream.destroy();
22
- });
23
- const handleError = (err) => {
24
- inputStream.destroy();
25
- outputStream.destroy();
26
- if (attempt < retries) {
27
- execute(++attempt);
28
- }
29
- else {
30
- reject(err);
31
- }
32
- };
33
- inputStream.on('error', handleError);
34
- outputStream.on('error', handleError);
16
+ await writeFile(filename, buffer);
35
17
  const shape = type === StickerTypes.SQUARE
36
18
  ? 'scale=320:-1:flags=lanczos,fps=10,crop=min(iw\\,ih):min(iw\\,ih)'
37
19
  : 'scale=320:-1:flags=lanczos,fps=20';
38
- ffmpeg(inputStream)
39
- .inputFormat(extType)
40
- .outputOptions(['-vf', shape, '-loop', '0', '-lossless', '0', '-t', '7', '-preset', 'ultrafast'])
41
- .toFormat('gif')
42
- .pipe(outputStream)
43
- .on('error', async (err) => {
44
- if (attempt < retries) {
45
- execute(++attempt);
46
- }
47
- else {
48
- reject(err);
49
- }
20
+ await new Promise((resolveFfmpeg, rejectFfmpeg) => {
21
+ ffmpeg(filename)
22
+ .inputFormat(extType)
23
+ .outputOptions(['-vf', shape, '-loop', '0', '-lossless', '0', '-t', '7', '-preset', 'ultrafast'])
24
+ .toFormat('gif')
25
+ .save(outputFilename)
26
+ .on('end', resolveFfmpeg)
27
+ .on('error', rejectFfmpeg);
50
28
  });
29
+ const gifBuffer = text ? await TextOnGif(outputFilename, text) : await readFile(outputFilename);
30
+ await Promise.all([unlink(filename), unlink(outputFilename)]);
31
+ resolve(gifBuffer);
51
32
  }
52
33
  catch (error) {
53
34
  if (attempt < retries) {
54
35
  execute(++attempt);
55
36
  }
56
37
  else {
38
+ await Promise.all([unlink(filename).catch(() => { }), unlink(outputFilename).catch(() => { })]);
57
39
  reject(error);
58
40
  }
59
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shibam/sticker-maker",
3
- "version": "1.1.11",
3
+ "version": "1.1.13",
4
4
  "description": "A package for creating stickers",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -35,10 +35,12 @@
35
35
  "dependencies": {
36
36
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
37
37
  "@types/fluent-ffmpeg": "^2.1.24",
38
+ "canvas": "^2.11.2",
38
39
  "file-type": "^19.4.0",
39
40
  "fluent-ffmpeg": "^2.1.3",
40
41
  "node-webpmux": "^3.2.0",
41
- "sharp": "^0.33.4"
42
+ "sharp": "^0.33.4",
43
+ "text-on-gif": "^2.0.13"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@types/node": "^20.10.0",