Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 45 additions & 5 deletions packages/media-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ A utility package for handling media files, specifically designed for processing
npm install @agent-infra/media-utils
```

## Base64ImageTool
## Base64ImageParser

> [!NOTE]
> Currently only supports parsing **static** base64 image formats: PNG, JPEG, WebP, GIF, and BMP
Expand All @@ -28,10 +28,10 @@ Supports both Node.js and browsers.
### Basic Usage

```typescript
import { Base64ImageTool } from '@agent-infra/media-utils';
import { Base64ImageParser } from '@agent-infra/media-utils/base64';

// Initialize with a base64 image string
const tool = new Base64ImageTool('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...');
const tool = new Base64ImageParser('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgA...');

// Get image type
const imageType = tool.getImageType(); // 'png' | 'jpeg' | 'webp' | 'gif' | 'bmp' | null
Expand All @@ -52,17 +52,57 @@ const dataUri = tool.getDataUri(); // 'data:image/png;base64,...'
### Example

```typescript
import { Base64ImageTool } from '@agent-infra/media-utils';
import { Base64ImageParser } from '@agent-infra/media-utils/base64';

const base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==';

const tool = new Base64ImageTool(base64Image);
const tool = new Base64ImageParser(base64Image);

console.log('Image type:', tool.getImageType()); // 'png'
console.log('Dimensions:', tool.getDimensions()); // { width: 1, height: 1 }
console.log('Pure base64:', tool.getPureBase64Image()); // 'iVBORw0KGgo...'
```

## ImageCompressor

A high-performance image compression utility that supports multiple formats and quality settings.

### Platform Compatibility

Node.js only.

### Basic Usage

```typescript
import { ImageCompressor } from '@agent-infra/media-utils/compressor';

// Create compressor with default options (WebP format, 80% quality)
const compressor = new ImageCompressor();

// Or with custom options
const customCompressor = new ImageCompressor({
quality: 90,
format: 'jpeg'
});

// Compress image buffer
const imageBuffer = new Uint8Array(/* your image data */);
const compressedBuffer = await compressor.compressToBuffer(imageBuffer);
const compressedBase64 = await compressor.compressToBase64(imageBuffer);
```


### Configuration Options

```typescript
interface ImageCompressionOptions {
quality?: number; // 1-100, default: 80
format?: 'jpeg' | 'png' | 'webp'; // default: 'webp'
width?: number; // optional width constraint
height?: number; // optional height constraint
}
```

## License

ISC
20 changes: 19 additions & 1 deletion packages/media-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "@agent-infra/media-utils",
"description": "media-utils",
"version": "0.0.1",
"sideEffects": false,
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
Expand All @@ -10,6 +11,16 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./base64": {
"types": "./dist/index.d.ts",
"import": "./dist/base64/index.mjs",
"require": "./dist/base64/index.js"
},
"./compressor": {
"types": "./dist/index.d.ts",
"import": "./dist/compressor.mjs",
"require": "./dist/compressor.js"
}
},
"files": [
Expand All @@ -26,10 +37,17 @@
},
"dependencies": {
"@agent-infra/logger": "0.0.2-beta.2",
"delay": "6.0.0"
"delay": "6.0.0",
"imagemin": "9.0.1",
"imagemin-mozjpeg": "10.0.0",
"imagemin-pngquant": "10.0.0",
"imagemin-webp": "8.0.0"
},
"devDependencies": {
"@types/node": "24.1.0",
"@types/imagemin": "9.0.1",
"@types/imagemin-mozjpeg": "8.0.4",
"@types/imagemin-webp": "7.0.3",
"typescript": "5.8.3",
"vitest": "3.2.4",
"@vitest/coverage-v8": "3.2.4",
Expand Down
6 changes: 3 additions & 3 deletions packages/media-utils/src/base64/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {

import type { ImageDimensions, ImageType } from '../type';

export class Base64ImageTool {
export class Base64ImageParser {
private pureBase64: string;
private buffer?: Uint8Array;

Expand Down Expand Up @@ -68,7 +68,7 @@ export class Base64ImageTool {
try {
switch (imageType) {
case 'png': {
const bytes = this.getHeaderBuffer(32); // 16-23
const bytes = this.getHeaderBuffer(24); // 16-23
this.dimensions = parsePngDimensions(bytes);
break;
}
Expand All @@ -93,7 +93,7 @@ export class Base64ImageTool {
break;
}
case 'bmp': {
const bytes = this.getHeaderBuffer(40); // 18-25
const bytes = this.getHeaderBuffer(32); // 18-25
this.dimensions = parseBmpDimensions(bytes);
break;
}
Expand Down
80 changes: 80 additions & 0 deletions packages/media-utils/src/compressor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
* SPDX-License-Identifier: Apache-2.0
*/
import imagemin from 'imagemin';
import imageminPngquant from 'imagemin-pngquant';
import imageminMozjpeg from 'imagemin-mozjpeg';
import imageminWebp from 'imagemin-webp';

import { ImageCompressionOptions } from './type';

/**
* High-performance image compression utility class
*/
export class ImageCompressor {
public readonly options: ImageCompressionOptions;

constructor(options?: ImageCompressionOptions) {
// Set default options
this.options = {
quality: options?.quality ?? 80,
format: options?.format ?? 'webp',
width: options?.width,
height: options?.height,
};
}

/**
* Compress image and return Buffer without writing to file
* @param imageBuffer Image Buffer
*/
async compressToBuffer(imageBuffer: Buffer) {
// Choose appropriate compression plugin
const plugins = this.getPluginsForFormat();

// Compress image
return await imagemin.buffer(imageBuffer, {
plugins,
});
}

async compressToBase64(imageBuffer: Buffer) {
// Compress image to Buffer
const compressedBuffer = await this.compressToBuffer(imageBuffer);

// Convert to Base64
return Buffer.from(compressedBuffer).toString('base64');
}

/**
* Select plugins based on target format
*/
private getPluginsForFormat() {
const quality = this.options.quality / 100; // Convert to 0-1 range (required by some plugins)

switch (this.options.format) {
case 'jpeg':
return [imageminMozjpeg({ quality: this.options.quality })];
case 'png':
return [
imageminPngquant({
quality: [quality, Math.min(quality + 0.2, 1)],
}),
];
case 'webp':
return [imageminWebp({ quality: this.options.quality })];
default:
return [imageminWebp({ quality: this.options.quality })];
}
}

/**
* Get formatted description of current compression options
*/
getOptionsDescription(): string {
return `Quality: ${this.options.quality}, Format: ${this.options.format}${
this.options.width ? `, Width: ${this.options.width}px` : ''
}${this.options.height ? `, Height: ${this.options.height}px` : ''}`;
}
}
1 change: 1 addition & 0 deletions packages/media-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
* SPDX-License-Identifier: Apache-2.0
*/
export * from './base64';
export * from './compressor';
export * from './type';
15 changes: 15 additions & 0 deletions packages/media-utils/src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,18 @@ export interface ImageDimensions {
width: number;
height: number;
}

export interface ImageCompressionOptions {
quality: number; // Compression quality (1-100)
format?: 'jpeg' | 'png' | 'webp';
width?: number; // Optional target width
height?: number; // Optional target height
}

export interface CompressionResult {
originalSize: number;
compressedSize: number;
compressionRatio: number;
path: string;
buffer: Buffer;
}
10 changes: 5 additions & 5 deletions packages/media-utils/test/base64.test.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Base64ImageTool Test</title>
<title>Base64ImageParser Test</title>
</head>
<body>
<h1>Base64ImageTool Test (Open in firefox to ignore CORS)</h1>
<h1>Base64ImageParser Test (Open in firefox to ignore CORS)</h1>

<h2>Original Image</h2>
<img id="originalImage" alt="Base64 Image">
Expand All @@ -18,16 +18,16 @@ <h2>Tool Results</h2>
<p style="word-break: break-all;"><strong>Pure Base64:</strong> <span id="pureBase64"></span></p>

<script type="module">
import { Base64ImageTool } from '../dist/index.mjs';
import { Base64ImageParser } from '../dist/index.mjs';

try {
const base64Image = 'data:image/webp;base64,UklGRhIPAABXRUJQVlA4TAUPAAAv74A3EL8HMZLtts1+UDn135L6UAeOAPjx24DYSJIiKbprjg04/x3ku+efcRVJkqXk7PIGC/gXhBE+7w52GnK2f4akX+lfVVO9tu3bHp3ApmArgT3uabNwAo5hM1AENtoI4O1DIHhFgipYGBC8oIKQktbioFEBAYlylKChoAIFW1iUoGFA4DBQIGhwcCrC++qqrqpsdeXV7Z0Lq69uD3owivrVdbPrs57GSNDT6Glsy9js7Pf09Pd24W8sZDno6aX/0ehp/I+FnmAkWOIIKQQCgaBg4aDgAlV4cLDQIBAUNEowMLCwMCAQCAQNgoKEhKwZHVUjcKg49OVqc8GxrjjVDae64+habH1hrcbtwXX41Hec6gYESJAiRYQIPv4QIkKIAM8I4COAD78IiR8qgI/AEiNSYqQWH3Hg4dGS5+SO+rfNzreZLDa83Eycd+u/Vdx99y9aZ+a2Wdev04FWOb1v1+x9/8MtP7QBLSwETJDAoarIUYIiBIKGhgJBowIuiFBXCBhoGCgQCBoGChoEBQUBDgkBCQkOAQGOzkDW4iR35li30pz37O6PYL8F6ztf653K/MrD+jAwL43fnxPyx1dm5pmRZQb2MZE98tUlVo8sx+DZI8tMMTeT72PyfUxxTpcf//84//yLy/fBbu8Nv/O01z6+O5/zq/g6v885Ctq2YWL+sHcwRMQENHE0KUmTyoMGbW3bjj2Ox7aZTDWKbds2Giz16F6Y2zCKxrYZJ2PsRdsdaOZ/v+t5nu84uhbRf0mw7dZt8ygP8kGRZd/DA0WuJcm2bdrSSWc2IDtgowu/O8/xvm3z6dv/p1VSH5wl2zbqunffufd55Yj+S4LANm4joPdyvF9BkgG8g/4nr5fQYtEHLQDBEEsMc6UAjL6+jqxrZ0lkl9cyqyLHI8LXrmP4o51hx2AQHs4w6/Yp8qn+llldJd4Qsj6CWXs/VjFsHQ36/fU0g2doKOqAnro+T1i1l8HudwUGjQMuiGeeMJi+TuZk10No7/eCQzuYz7aX48xnbY8LxMlb0LePRshQ2sWgt9IDDm6BfvZskvkstOdcMC+MM5i6TCK3CQotee5JR/ZBSR+goZZhF4Lhe1C4N0w+FT0MusucsXwP89n5Fhpq6HehvDrFYPw8iYI2KDRkO2Lxduaz/cUE81nTXQrHyCMo3DpJMp0aCJ3FbgjdRCV9goZ+j7lwnLgO4ckZMhRThWucCgeviWSwhxpqGnISnn0GFb6CFm6A0FbggqMHoKQ3RQb1fU6EoTtQeDhHhrJuBj0VDli0DfoZNVTdWXIyXppgMHGJRF4zFJpyAyJsw34GT6n1a1veCTH7AAp3UPJe2QuR99KAOEzx1/fU+nVQrkWvTDN4fo5EP1W4PlDhpTsh/voKWr/W9To5Tz+Gwo0TZOE6CB1FAcTnVuh//OeT0FBHLgJO3ITweJ4MJRR5rw4gPvdrE59ReJ4i71dQ8t5Ikff8z2HlHkp8FjHxGQKlxfv9WfIp74EKl38GS3ZAP8PEZ7eLyFeTEHm/QCK/FQqNObj4/ESJzzEhBPpJQ/r6SZ0lC/jrkUhV4rN5OPr26DWo8NMzevtJK3ZT4rOIic/oPEOR92sa+0mFGsVnVwnA0C2KvI+i5B3rJ5Hi8ykoPglepAq/QgtT5L05Lyh49V5IfL4v0OIz4oPvU/0klLxX9FL9JEx8bgPFp5BS/aQX50kUYv2k+K2U+JzExCeFMYq83zxJPrVU4YR92sQnhRM3qH7SGNpP6mSQlaxNfHI891wfec+hyHt6jCbx2eNAnLoLhQcsee+GQqIeE6P4R3ySvDSpsJ/UAoW0aD0mxohDMaexn1QFFc6M0yI+GwYcTMqUfY6asv3tUEiO0iE+a7odjdNPIfJ+AyXvdRAyYnWKTz3k/RHaTxrshJ4kjyYGKD4R+oUpmx6twcTodwh8w5TNTNQhPjV4WWE/qaAVCqlR0uKzqg0aMmLKUoVtiE8bpuxAh2zhba+5QwyEkqbsa4WmbEaMrInRmUPgM6ZslmPhp+Ahhh4vUqbsK4WmbFq0pPiE1q/Cpuxd2JQVS972irvJpkrKlH1xju0nYYXFxGe7eEhbP0mhKZsRq1t8KjBlx1BTVoi87wZvsmnz3Auf6CdtfzmBiU91DN+lTFmV/SSpSTZ9vqIKX1RoyqZFSUyyQeLToClbxZP3SF0mhhpTVuOwfXIULT6rIfFJmLJPoaDRlM2IETAxbGDotkJTtrSLIu+0+IQOMRSZspOvVPaTFIpPXabssD5TNjORnmSzw8sUeT+vsp/ETrIZwtgjs6ZsZqzEJJuNB9+AwpPTaD+pkypMbmdCqM+UfY2S9wYq+W/MxMgZw6k7lCk7ByIkJRMqnC5ghxiCy8qw/fK/06GQqDQkPu2asgsm2ZKyGMQy1OtMxDJsyi6YZIvJgEISEp8GcfqJqmH74DV7IyKikiHEs8ghhkWcvKnJlF0wyRaXCT0pQnza5PkXcqas+3amqDQqVHI32bQVxobth13gvp0pEUquyEQXn1b5akrOlHXfzhSTTiVXGrjJptuUHXKB+3amJAixLCk+dZH364LD9u7bmWIzqGT1m2y2TdmQdfs/h6hUrLD2IYZFU9ZtOxNaOK0sPkVNWbl+kvt2pug0jLzLX2cyjtn78qbswc0RARZI3uU32azzypS0KRsScDsTSd4rFcWnWVPWcTsTSt4J8WmZvD8+7ZG/Rogkp9RushkxZaevuPLXz21nkigs+DVcfGol75KmrMt2Jpi8p5XEpxlTdvyim/jcFeGGaI68Q+JTYT+JMmXvDLnx1wBLmLxXd1of0vAmsjAP2+JjRfpJrXnnDc88gcL1E57fjSZC3kPEp23IvIls1P3daCoK16keYqgdtp+8HCgp4LZ4KfJuQnxaGbYPWedxL2IS1k+CxacmU5Z7ExnxTgu8nxTwOpNnmIeH7aFt8clYYfImm21TNnRzhGfEYsn/HWJ4x7PPqX7SQv66NjIAZMk7Jz5tm7LLkG3xiZgpi61fjZqy1y+Q3mjRmCkrKz6tDNuv3wpZeGDk/d+/gmwu8k1kaw5Rnhb/xDEnMgS2Tdkt4eLTfTtTEnMiM4qT3JvISG+0bAxzIvO+bJuyt0lvtErqW0u+d9g2Ze+jBtmZCsyJzOuybcrefYwaZFdSw405RjHykMCdR7BBdgp2InMl77ZN2aMPdsDeaNk45zCvcplwIru3HzfIpobbCo1i6DabBL0Wn6EeXGk1+eI4lgR6WlQmKCeyXKOYvY8lkZ4WaejBXWVWw5UpLAnytEAf3GC18PxjTz97jBtks4U7BqyS9xsesCoZgrbFZ2Ocwzy2rAzbH723Q8cbDVy8t5ol73dZ/kpsiycX7xVWky9NYH8L9LSoTGg5zNs2ZY8+gFOSZJK7Bq2S99fTXv4W7o1GL97rzZL3J278VdkgO6kS02TKlNXAX4UK91ZZTb4wLi0+3ZIVYppsm7L39usYZEuQ9+5yq+HylJj4dERZ3E965Im/8t5o5XI/CRWfrtvipch77X8L2zZl7+43YpCdRGOabJuydx5bMcjOxMrc4n1cWnz6L3mffUDzV8TT4juKvJdaTb4yLSM+Iw77nSn7mDYxEOo3ZS04kR0D168Mvu/2O1MWE5+gp8UPjX5nypLiE/NGK+/xOVP2zSd1pyTlt/gbeZ//SPVBUYPs6l5fI+8rdkP4VqAMsuHjty1A7rTN71OoQXZ2vV+ZsuAk244nnzGDbPqYJnvgTkna/xWLyuWPabKHpbuopGnKG03gmCZzCN+KJWEG2QLHb5soHOi0TSpJwRutDzumacAYVnFJGt5oxZ2UKWsLh3ZQP5umvNFkXufbWmAJB7dgSUoG2WU9/tBPCrSdCUr6ghlkyx2/bQfL90BJ36rUDLKren3GlF28XQ1/lb/OVNjuL6Zs6GaMv2IG2YLHNLUXGeGvayMZ7MPWr7+KHtNUZQPLDqjir/V90nfY8BXxqWyQXdFj15R1uXKBLvGZ14/f1p90mBOf2gbZfdzx234rPg3ENNX2+ar47MiZ6LDhD+LzC2aQbaLDRqVy8blHm/iMhjw/6Cct0SY+jcQ0dZeqFp+6+Gt7zkz8drbepCORWJIVg+y+OuvD9sspEwMUnwCLOylT1ifFZ4npsGF62D50kzbxScU0GTZlg9doMzFsxW835WjEURPiM0isn9Q56APi04RBNmvKZvue+DTYYUMeUvfuoYZm5tFuYnZMWc337pletBjT1JxnUnw+n4Q+F45ajGnqLlNmYui6d8/cyq0rcPy2GVNW7b17ZlfwDht2TFmBe/dgQ2ZjmmoVic/N3CQbg4Wj/ziRwTFNxkzZEO7ePdiQNKZJQz+pV4Mpq/HePbPLtruJaeGv2HYmakitw4ahYfsw7N492NCqUjexwnYzw/bovXuo/0uvw4adYfuj3LvRoL8th9j5iuO37ZmyQvc3o4aOhphjXkYfbMWUDVuv6/J1c6uCmCaQ5ZpMWQslPrkhxmHetil7aIcy8elJNzHZJO4Sn9j6VeIwb7ObmOwkmzL+qt9hQ/+w/UruEp8UfzUQv92tvZ+0eLsy8cnEb9s2ZUO1ic9VsZ2vyc6puifZvuSSTHTYKNBtyi7jLvGpOTQzQ/eT+moxU9YPxKfkHTZEhu2r+/RuZ3r6ERvydkwTSt71mrLBh7WJT+ZuYrZN2aXaxCfTYUOZKZtrXXxGjmmC77Chx5RVfInPuVVbxa3bVZqyqzATAxtCYpo0Xtxao/jk1q/WilsPdul7E9lBdeLT025izXl2TQymw4YRU9aTJtdLVPy2EVPWjybXZ8H4bXAJXtzajybXSEyTXlPWhbz70OSaiWkSMGUF+0keNLlmYppETNkuMVPWgybXbEyTxn6SC3k33+TacDcxLaZssLqbXDPx2+ZMWT+aXJ/lOmxoNGW1TLLhrzMd97ibWLUl8dk6wsc0TV+10U/yo8n1CZ+LWzvAbpNrpsOGpCkLvonMpbDZJtceFLemCpcYEZ/Nw0w3Mdum7ErtN7k+/9Tsxa3b+l2xS5f47Cp50k1M1pSVnWRjKhdcmNB3ces84X7SHmTtelfLsFn2OtPs3fEXxBq/zV7curMLWR2DriYkso70QhyU2tteYNY59B/nlpYxq9ANDgA=';

console.log('Testing Base64ImageTool...');
console.log('Testing Base64ImageParser...');

document.getElementById('originalImage').src = base64Image;

const tool = new Base64ImageTool(base64Image);
const tool = new Base64ImageParser(base64Image);
const imageType = tool.getImageType();
const dimensions = tool.getDimensions();
const pureBase64 = tool.getPureBase64Image();
Expand Down
Loading