@harpiaresearch/vref 0.0.0-stage → 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.
- package/LICENSE +28 -0
- package/README.md +365 -2
- package/dist/VREFReader.d.ts +22 -0
- package/dist/VREFReader.js +185 -0
- package/dist/VREFWriter.d.ts +46 -0
- package/dist/VREFWriter.js +225 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +216 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +16 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.js +18 -0
- package/package.json +46 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024, Harpia AI Research
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,366 @@
|
|
|
1
|
-
#
|
|
1
|
+
# VREF — Visual Reference Format
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Standardized container for curated visual references in AI image generation**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@harpiaresearch/vref)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
Developed by [Harpia AI Research](https://harpiaresearch.cc) 🇧🇷
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## What is VREF?
|
|
13
|
+
|
|
14
|
+
VREF (Visual Reference Format) is an open standard for packaging and sharing curated visual references for AI image generation platforms.
|
|
15
|
+
|
|
16
|
+
Instead of uploading multiple loose images or struggling to describe complex visual concepts in text, users can:
|
|
17
|
+
|
|
18
|
+
1. **Package** reference images into a single `.vref` file
|
|
19
|
+
2. **Upload** the `.vref` to any supporting AI platform
|
|
20
|
+
3. **Select** how many random references to use (via slider)
|
|
21
|
+
4. **Generate** images with visual context
|
|
22
|
+
|
|
23
|
+
### The Problem
|
|
24
|
+
|
|
25
|
+
When generating images with AI, users often:
|
|
26
|
+
- ❌ Know what they want visually but can't describe it in words
|
|
27
|
+
- ❌ Need to upload 10-50 reference images individually
|
|
28
|
+
- ❌ Can't easily share or reuse reference collections
|
|
29
|
+
- ❌ Struggle to communicate style, mood, and composition
|
|
30
|
+
|
|
31
|
+
### The Solution
|
|
32
|
+
|
|
33
|
+
VREF provides:
|
|
34
|
+
- ✅ **Single file** container for multiple images
|
|
35
|
+
- ✅ **Validated** image-only format
|
|
36
|
+
- ✅ **Structured metadata** (tags, description, license)
|
|
37
|
+
- ✅ **Portable** and shareable
|
|
38
|
+
- ✅ **Random sampling** built-in
|
|
39
|
+
- ✅ **Open standard** anyone can implement
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install @harpiaresearch/vref
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or use directly with `npx`:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npx create-vref
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Quick Start
|
|
58
|
+
|
|
59
|
+
### Creating a .vref file (CLI)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# From a directory of images
|
|
63
|
+
npx create-vref ./my-images -o references.vref
|
|
64
|
+
|
|
65
|
+
# With metadata
|
|
66
|
+
npx create-vref ./images -o pack.vref \
|
|
67
|
+
-n "My Reference Pack" \
|
|
68
|
+
-d "Collection of visual references for X" \
|
|
69
|
+
-t "style,mood,theme" \
|
|
70
|
+
-a "Your Name" \
|
|
71
|
+
-l "CC-BY-4.0"
|
|
72
|
+
|
|
73
|
+
# From a single image
|
|
74
|
+
npx create-vref image.png -o single.vref
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Using the library (JavaScript/TypeScript)
|
|
78
|
+
|
|
79
|
+
**Creating a .vref file:**
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { VREFWriter } from '@harpiaresearch/vref';
|
|
83
|
+
|
|
84
|
+
const writer = new VREFWriter({
|
|
85
|
+
fileName: 'my-pack.vref',
|
|
86
|
+
description: 'Collection of RPG character references',
|
|
87
|
+
tags: ['rpg', 'characters', 'fantasy'],
|
|
88
|
+
author: 'Your Name',
|
|
89
|
+
license: 'CC-BY-4.0',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Add images from directory
|
|
93
|
+
writer.addImagesFromDirectory('./images', true);
|
|
94
|
+
|
|
95
|
+
// Or add individual images
|
|
96
|
+
writer.addImageFromFile('./image1.png');
|
|
97
|
+
writer.addImage('image2.jpg', imageBuffer);
|
|
98
|
+
|
|
99
|
+
// Write the .vref file
|
|
100
|
+
writer.write('output.vref');
|
|
101
|
+
|
|
102
|
+
console.log(`Created .vref with ${writer.getImageCount()} images`);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Reading a .vref file:**
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { VREFReader } from '@harpiaresearch/vref';
|
|
109
|
+
|
|
110
|
+
// Read the entire .vref file
|
|
111
|
+
const vref = VREFReader.read('references.vref', { loadImages: true });
|
|
112
|
+
|
|
113
|
+
console.log(`Loaded ${vref.manifest.imageCount} images`);
|
|
114
|
+
console.log(`Description: ${vref.manifest.metadata?.description}`);
|
|
115
|
+
|
|
116
|
+
// Get random sample of 3 images
|
|
117
|
+
const randomImages = vref.sample(3);
|
|
118
|
+
randomImages.forEach((buffer, idx) => {
|
|
119
|
+
console.log(`Image ${idx + 1}: ${buffer.length} bytes`);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Get specific image
|
|
123
|
+
const image = vref.getImage('photo1.png');
|
|
124
|
+
if (image) {
|
|
125
|
+
// Do something with the image buffer
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Read only the manifest:**
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { VREFReader } from '@harpiaresearch/vref';
|
|
133
|
+
|
|
134
|
+
const manifest = VREFReader.readManifest('references.vref');
|
|
135
|
+
console.log(manifest);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Validate a .vref file:**
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { VREFReader } from '@harpiaresearch/vref';
|
|
142
|
+
|
|
143
|
+
const result = VREFReader.validate('references.vref');
|
|
144
|
+
if (result.valid) {
|
|
145
|
+
console.log('✅ Valid .vref file');
|
|
146
|
+
} else {
|
|
147
|
+
console.error('❌ Invalid .vref file:');
|
|
148
|
+
result.errors.forEach(err => console.error(` - ${err}`));
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Use Cases
|
|
155
|
+
|
|
156
|
+
### 1. **AI Image Generation Platforms**
|
|
157
|
+
|
|
158
|
+
Users upload `.vref` files to platforms like:
|
|
159
|
+
- Midjourney
|
|
160
|
+
- Stable Diffusion
|
|
161
|
+
- DALL-E
|
|
162
|
+
- Replicate
|
|
163
|
+
- Custom AI platforms
|
|
164
|
+
|
|
165
|
+
The platform extracts random images and sends them as visual context along with the user's text prompt.
|
|
166
|
+
|
|
167
|
+
### 2. **Game Development**
|
|
168
|
+
|
|
169
|
+
Distribute reference packs:
|
|
170
|
+
- `rpg-sprites.vref` — Character sprites for RPG games
|
|
171
|
+
- `platformer-tiles.vref` — Tile sets for platformers
|
|
172
|
+
- `ui-elements.vref` — UI design references
|
|
173
|
+
|
|
174
|
+
### 3. **Training ML Models**
|
|
175
|
+
|
|
176
|
+
Use `.vref` files as datasets:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { VREFReader } from '@harpiaresearch/vref';
|
|
180
|
+
import { PixelGenModel } from '@harpiaresearch/pixelgen';
|
|
181
|
+
|
|
182
|
+
const dataset = VREFReader.read('training-data.vref', { loadImages: true });
|
|
183
|
+
const model = new PixelGenModel(3, 3);
|
|
184
|
+
|
|
185
|
+
// Train with VREF data
|
|
186
|
+
// ...
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### 4. **Design & Creative Work**
|
|
190
|
+
|
|
191
|
+
Share mood boards and references:
|
|
192
|
+
- `architecture-modern.vref`
|
|
193
|
+
- `color-palettes-warm.vref`
|
|
194
|
+
- `typography-references.vref`
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## File Format Specification
|
|
199
|
+
|
|
200
|
+
A `.vref` file is a ZIP container with:
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
my-pack.vref (ZIP)
|
|
204
|
+
├── manifest.json
|
|
205
|
+
├── image1.png
|
|
206
|
+
├── image2.jpg
|
|
207
|
+
├── image3.webp
|
|
208
|
+
└── ...
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### `manifest.json` Structure
|
|
212
|
+
|
|
213
|
+
```json
|
|
214
|
+
{
|
|
215
|
+
"version": "1.0.0",
|
|
216
|
+
"format": "VREF",
|
|
217
|
+
"fileName": "my-pack.vref",
|
|
218
|
+
"imageCount": 24,
|
|
219
|
+
"images": [
|
|
220
|
+
{
|
|
221
|
+
"path": "image1.png",
|
|
222
|
+
"format": "png",
|
|
223
|
+
"size": 45678,
|
|
224
|
+
"width": 1920,
|
|
225
|
+
"height": 1080
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
"createdAt": "2024-10-12T10:30:00.000Z",
|
|
229
|
+
"metadata": {
|
|
230
|
+
"description": "Collection of X references",
|
|
231
|
+
"tags": ["tag1", "tag2"],
|
|
232
|
+
"author": "Author Name",
|
|
233
|
+
"license": "CC-BY-4.0"
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Supported Image Formats
|
|
239
|
+
|
|
240
|
+
- ✅ PNG (`.png`)
|
|
241
|
+
- ✅ JPEG (`.jpg`, `.jpeg`)
|
|
242
|
+
- ✅ WebP (`.webp`)
|
|
243
|
+
- ✅ GIF (`.gif`)
|
|
244
|
+
- ✅ AVIF (`.avif`)
|
|
245
|
+
|
|
246
|
+
Any other file type is **rejected** with an error.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## API Reference
|
|
251
|
+
|
|
252
|
+
### `VREFWriter`
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
class VREFWriter {
|
|
256
|
+
constructor(options: VREFCreateOptions);
|
|
257
|
+
|
|
258
|
+
addImageFromFile(filePath: string): void;
|
|
259
|
+
addImage(fileName: string, buffer: Buffer): void;
|
|
260
|
+
addImagesFromDirectory(dirPath: string, recursive?: boolean): void;
|
|
261
|
+
|
|
262
|
+
write(outputPath: string): void;
|
|
263
|
+
getImageCount(): number;
|
|
264
|
+
clear(): void;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### `VREFReader`
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
class VREFReader {
|
|
272
|
+
static read(filePath: string, options?: VREFReadOptions): VREFReadResult;
|
|
273
|
+
static readManifest(filePath: string): VREFManifest;
|
|
274
|
+
static validate(filePath: string): { valid: boolean; errors: string[] };
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### Types
|
|
279
|
+
|
|
280
|
+
See [src/types.ts](src/types.ts) for complete type definitions.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Platform Integration
|
|
285
|
+
|
|
286
|
+
### For AI Platform Developers
|
|
287
|
+
|
|
288
|
+
Integrating VREF support:
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import { VREFReader } from '@harpiaresearch/vref';
|
|
292
|
+
|
|
293
|
+
// 1. User uploads .vref file
|
|
294
|
+
const vref = VREFReader.read(uploadedFile, { loadImages: true });
|
|
295
|
+
|
|
296
|
+
// 2. Show slider: "How many references? (1 - {vref.manifest.imageCount})"
|
|
297
|
+
const userSelectedCount = getUserSliderValue(); // e.g., 3
|
|
298
|
+
|
|
299
|
+
// 3. Sample random images
|
|
300
|
+
const selectedImages = vref.sample(userSelectedCount);
|
|
301
|
+
|
|
302
|
+
// 4. Get user's text prompt
|
|
303
|
+
const userPrompt = getUserPrompt(); // "A warrior character in pixel art style"
|
|
304
|
+
|
|
305
|
+
// 5. Send to your AI model
|
|
306
|
+
await generateImage({
|
|
307
|
+
prompt: userPrompt,
|
|
308
|
+
referenceImages: selectedImages,
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
That's it! 🎉
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Examples
|
|
317
|
+
|
|
318
|
+
See the [examples/](examples/) directory for:
|
|
319
|
+
- Creating `.vref` files
|
|
320
|
+
- Reading and sampling images
|
|
321
|
+
- Platform integration examples
|
|
322
|
+
- PixelGen integration
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## Contributing
|
|
327
|
+
|
|
328
|
+
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
329
|
+
|
|
330
|
+
---
|
|
331
|
+
|
|
332
|
+
## License
|
|
333
|
+
|
|
334
|
+
BSD 3-Clause License
|
|
335
|
+
|
|
336
|
+
Copyright (c) 2024, Harpia AI Research
|
|
337
|
+
|
|
338
|
+
See [LICENSE](LICENSE) for full text.
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## Citation
|
|
343
|
+
|
|
344
|
+
If you use VREF in your research or project:
|
|
345
|
+
|
|
346
|
+
```bibtex
|
|
347
|
+
@software{vref2024,
|
|
348
|
+
title = {VREF: Visual Reference Format for AI Image Generation},
|
|
349
|
+
author = {Harpia AI Research},
|
|
350
|
+
year = {2024},
|
|
351
|
+
url = {https://github.com/Harpia-AI-Research/vref}
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Links
|
|
358
|
+
|
|
359
|
+
- 🌐 Website: [harpiaresearch.cc](https://harpiaresearch.cc)
|
|
360
|
+
- 📦 npm: [@harpiaresearch/vref](https://www.npmjs.com/package/@harpiaresearch/vref)
|
|
361
|
+
- 📄 Specification: [docs/SPECIFICATION.md](docs/SPECIFICATION.md)
|
|
362
|
+
- 💬 Issues: [GitHub Issues](https://github.com/Harpia-AI-Research/vref/issues)
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
**Made with 💚💛 by Harpia AI Research — São Paulo, Brazil** 🇧🇷
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VREFReader - Reads .vref files
|
|
3
|
+
* Harpia AI Research
|
|
4
|
+
*/
|
|
5
|
+
import { VREFManifest, VREFReadOptions, VREFReadResult } from './types';
|
|
6
|
+
export declare class VREFReader {
|
|
7
|
+
/**
|
|
8
|
+
* Read a .vref file
|
|
9
|
+
*/
|
|
10
|
+
static read(filePath: string, options?: VREFReadOptions): VREFReadResult;
|
|
11
|
+
/**
|
|
12
|
+
* Read only the manifest without loading images
|
|
13
|
+
*/
|
|
14
|
+
static readManifest(filePath: string): VREFManifest;
|
|
15
|
+
/**
|
|
16
|
+
* Validate a .vref file
|
|
17
|
+
*/
|
|
18
|
+
static validate(filePath: string): {
|
|
19
|
+
valid: boolean;
|
|
20
|
+
errors: string[];
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* VREFReader - Reads .vref files
|
|
4
|
+
* Harpia AI Research
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.VREFReader = void 0;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const adm_zip_1 = __importDefault(require("adm-zip"));
|
|
46
|
+
class VREFReader {
|
|
47
|
+
/**
|
|
48
|
+
* Read a .vref file
|
|
49
|
+
*/
|
|
50
|
+
static read(filePath, options = {}) {
|
|
51
|
+
if (!fs.existsSync(filePath)) {
|
|
52
|
+
throw new Error(`File not found: ${filePath}`);
|
|
53
|
+
}
|
|
54
|
+
const zip = new adm_zip_1.default(filePath);
|
|
55
|
+
const zipEntries = zip.getEntries();
|
|
56
|
+
// Find and parse manifest
|
|
57
|
+
const manifestEntry = zipEntries.find(entry => entry.entryName === 'manifest.json');
|
|
58
|
+
if (!manifestEntry) {
|
|
59
|
+
throw new Error('Invalid .vref file: manifest.json not found');
|
|
60
|
+
}
|
|
61
|
+
const manifestData = manifestEntry.getData().toString('utf8');
|
|
62
|
+
const manifest = JSON.parse(manifestData);
|
|
63
|
+
// Validate manifest
|
|
64
|
+
if (manifest.format !== 'VREF') {
|
|
65
|
+
throw new Error('Invalid .vref file: incorrect format identifier');
|
|
66
|
+
}
|
|
67
|
+
// Load images if requested
|
|
68
|
+
const images = new Map();
|
|
69
|
+
if (options.loadImages) {
|
|
70
|
+
for (const imageInfo of manifest.images) {
|
|
71
|
+
const entry = zipEntries.find(e => e.entryName === imageInfo.path);
|
|
72
|
+
if (entry) {
|
|
73
|
+
images.set(imageInfo.path, entry.getData());
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Helper function to get specific image
|
|
78
|
+
const getImage = (path) => {
|
|
79
|
+
if (images.has(path)) {
|
|
80
|
+
return images.get(path);
|
|
81
|
+
}
|
|
82
|
+
// Load on-demand if not already loaded
|
|
83
|
+
const entry = zipEntries.find(e => e.entryName === path);
|
|
84
|
+
if (entry) {
|
|
85
|
+
const buffer = entry.getData();
|
|
86
|
+
images.set(path, buffer);
|
|
87
|
+
return buffer;
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
};
|
|
91
|
+
// Helper function to sample N random images
|
|
92
|
+
const sample = (count) => {
|
|
93
|
+
const imagePaths = manifest.images.map(img => img.path);
|
|
94
|
+
const sampledPaths = [];
|
|
95
|
+
// Fisher-Yates shuffle for random sampling
|
|
96
|
+
const shuffled = [...imagePaths];
|
|
97
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
98
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
99
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
100
|
+
}
|
|
101
|
+
// Take first N
|
|
102
|
+
const selected = shuffled.slice(0, Math.min(count, shuffled.length));
|
|
103
|
+
// Load and return buffers
|
|
104
|
+
return selected.map(path => {
|
|
105
|
+
const buffer = getImage(path);
|
|
106
|
+
if (!buffer) {
|
|
107
|
+
throw new Error(`Failed to load image: ${path}`);
|
|
108
|
+
}
|
|
109
|
+
return buffer;
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
manifest,
|
|
114
|
+
images: options.loadImages ? images : undefined,
|
|
115
|
+
sample,
|
|
116
|
+
getImage,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Read only the manifest without loading images
|
|
121
|
+
*/
|
|
122
|
+
static readManifest(filePath) {
|
|
123
|
+
const result = VREFReader.read(filePath, { loadImages: false });
|
|
124
|
+
return result.manifest;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Validate a .vref file
|
|
128
|
+
*/
|
|
129
|
+
static validate(filePath) {
|
|
130
|
+
const errors = [];
|
|
131
|
+
try {
|
|
132
|
+
if (!fs.existsSync(filePath)) {
|
|
133
|
+
errors.push('File does not exist');
|
|
134
|
+
return { valid: false, errors };
|
|
135
|
+
}
|
|
136
|
+
const zip = new adm_zip_1.default(filePath);
|
|
137
|
+
const zipEntries = zip.getEntries();
|
|
138
|
+
// Check for manifest
|
|
139
|
+
const manifestEntry = zipEntries.find(entry => entry.entryName === 'manifest.json');
|
|
140
|
+
if (!manifestEntry) {
|
|
141
|
+
errors.push('manifest.json not found');
|
|
142
|
+
return { valid: false, errors };
|
|
143
|
+
}
|
|
144
|
+
// Parse manifest
|
|
145
|
+
let manifest;
|
|
146
|
+
try {
|
|
147
|
+
const manifestData = manifestEntry.getData().toString('utf8');
|
|
148
|
+
manifest = JSON.parse(manifestData);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
errors.push('Failed to parse manifest.json');
|
|
152
|
+
return { valid: false, errors };
|
|
153
|
+
}
|
|
154
|
+
// Validate format
|
|
155
|
+
if (manifest.format !== 'VREF') {
|
|
156
|
+
errors.push('Invalid format identifier (expected "VREF")');
|
|
157
|
+
}
|
|
158
|
+
// Validate version
|
|
159
|
+
if (!manifest.version) {
|
|
160
|
+
errors.push('Missing version field');
|
|
161
|
+
}
|
|
162
|
+
// Validate image count
|
|
163
|
+
const actualImageCount = zipEntries.filter(e => e.entryName !== 'manifest.json').length;
|
|
164
|
+
if (manifest.imageCount !== actualImageCount) {
|
|
165
|
+
errors.push(`Image count mismatch: manifest says ${manifest.imageCount}, found ${actualImageCount}`);
|
|
166
|
+
}
|
|
167
|
+
// Validate all images listed in manifest exist
|
|
168
|
+
for (const imageInfo of manifest.images) {
|
|
169
|
+
const entry = zipEntries.find(e => e.entryName === imageInfo.path);
|
|
170
|
+
if (!entry) {
|
|
171
|
+
errors.push(`Image listed in manifest not found: ${imageInfo.path}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
valid: errors.length === 0,
|
|
176
|
+
errors,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
errors.push(`Unexpected error: ${error}`);
|
|
181
|
+
return { valid: false, errors };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exports.VREFReader = VREFReader;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VREFWriter - Creates .vref files
|
|
3
|
+
* Harpia AI Research
|
|
4
|
+
*/
|
|
5
|
+
import { VREFCreateOptions } from './types';
|
|
6
|
+
export declare class VREFWriter {
|
|
7
|
+
private images;
|
|
8
|
+
private options;
|
|
9
|
+
constructor(options: VREFCreateOptions);
|
|
10
|
+
/**
|
|
11
|
+
* Add an image from a file path
|
|
12
|
+
*/
|
|
13
|
+
addImageFromFile(filePath: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* Add an image from a buffer
|
|
16
|
+
*/
|
|
17
|
+
addImage(fileName: string, buffer: Buffer): void;
|
|
18
|
+
/**
|
|
19
|
+
* Add multiple images from a directory
|
|
20
|
+
*/
|
|
21
|
+
addImagesFromDirectory(dirPath: string, recursive?: boolean): void;
|
|
22
|
+
/**
|
|
23
|
+
* Write the .vref file
|
|
24
|
+
*/
|
|
25
|
+
write(outputPath: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Validate image format
|
|
28
|
+
*/
|
|
29
|
+
private isValidImageFormat;
|
|
30
|
+
/**
|
|
31
|
+
* Validate image buffer by checking magic bytes
|
|
32
|
+
*/
|
|
33
|
+
private isValidImageBuffer;
|
|
34
|
+
/**
|
|
35
|
+
* Get image dimensions (basic implementation for PNG and JPEG)
|
|
36
|
+
*/
|
|
37
|
+
private getImageDimensions;
|
|
38
|
+
/**
|
|
39
|
+
* Get current image count
|
|
40
|
+
*/
|
|
41
|
+
getImageCount(): number;
|
|
42
|
+
/**
|
|
43
|
+
* Clear all images
|
|
44
|
+
*/
|
|
45
|
+
clear(): void;
|
|
46
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* VREFWriter - Creates .vref files
|
|
4
|
+
* Harpia AI Research
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.VREFWriter = void 0;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const adm_zip_1 = __importDefault(require("adm-zip"));
|
|
47
|
+
const types_1 = require("./types");
|
|
48
|
+
class VREFWriter {
|
|
49
|
+
constructor(options) {
|
|
50
|
+
this.images = new Map();
|
|
51
|
+
this.options = options;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Add an image from a file path
|
|
55
|
+
*/
|
|
56
|
+
addImageFromFile(filePath) {
|
|
57
|
+
if (!fs.existsSync(filePath)) {
|
|
58
|
+
throw new Error(`File not found: ${filePath}`);
|
|
59
|
+
}
|
|
60
|
+
const buffer = fs.readFileSync(filePath);
|
|
61
|
+
const fileName = path.basename(filePath);
|
|
62
|
+
this.addImage(fileName, buffer);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Add an image from a buffer
|
|
66
|
+
*/
|
|
67
|
+
addImage(fileName, buffer) {
|
|
68
|
+
const ext = path.extname(fileName).toLowerCase().slice(1);
|
|
69
|
+
if (!this.isValidImageFormat(ext)) {
|
|
70
|
+
throw new Error(`Unsupported format: ${ext}. Only images are allowed: ${types_1.SUPPORTED_IMAGE_FORMATS.join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
// Validate that it's actually an image by checking magic bytes
|
|
73
|
+
if (!this.isValidImageBuffer(buffer, ext)) {
|
|
74
|
+
throw new Error(`File ${fileName} does not appear to be a valid ${ext} image`);
|
|
75
|
+
}
|
|
76
|
+
this.images.set(fileName, buffer);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Add multiple images from a directory
|
|
80
|
+
*/
|
|
81
|
+
addImagesFromDirectory(dirPath, recursive = false) {
|
|
82
|
+
if (!fs.existsSync(dirPath)) {
|
|
83
|
+
throw new Error(`Directory not found: ${dirPath}`);
|
|
84
|
+
}
|
|
85
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
88
|
+
if (entry.isDirectory() && recursive) {
|
|
89
|
+
this.addImagesFromDirectory(fullPath, recursive);
|
|
90
|
+
}
|
|
91
|
+
else if (entry.isFile()) {
|
|
92
|
+
const ext = path.extname(entry.name).toLowerCase().slice(1);
|
|
93
|
+
if (this.isValidImageFormat(ext)) {
|
|
94
|
+
try {
|
|
95
|
+
this.addImageFromFile(fullPath);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.warn(`Skipping ${entry.name}: ${error}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Write the .vref file
|
|
106
|
+
*/
|
|
107
|
+
write(outputPath) {
|
|
108
|
+
if (this.images.size === 0) {
|
|
109
|
+
throw new Error('No images added. Cannot create empty .vref file.');
|
|
110
|
+
}
|
|
111
|
+
const zip = new adm_zip_1.default();
|
|
112
|
+
const imageMetadataList = [];
|
|
113
|
+
// Add all images to ZIP
|
|
114
|
+
for (const [fileName, buffer] of this.images) {
|
|
115
|
+
zip.addFile(fileName, buffer);
|
|
116
|
+
const ext = path.extname(fileName).toLowerCase().slice(1);
|
|
117
|
+
const dimensions = this.getImageDimensions(buffer, ext);
|
|
118
|
+
imageMetadataList.push({
|
|
119
|
+
path: fileName,
|
|
120
|
+
format: ext,
|
|
121
|
+
size: buffer.length,
|
|
122
|
+
width: dimensions?.width,
|
|
123
|
+
height: dimensions?.height,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
// Create manifest
|
|
127
|
+
const manifest = {
|
|
128
|
+
version: '1.0.0',
|
|
129
|
+
format: 'VREF',
|
|
130
|
+
fileName: path.basename(outputPath),
|
|
131
|
+
imageCount: this.images.size,
|
|
132
|
+
images: imageMetadataList,
|
|
133
|
+
createdAt: new Date().toISOString(),
|
|
134
|
+
metadata: {
|
|
135
|
+
description: this.options.description,
|
|
136
|
+
tags: this.options.tags,
|
|
137
|
+
author: this.options.author,
|
|
138
|
+
license: this.options.license,
|
|
139
|
+
...this.options.metadata,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
// Add manifest as JSON
|
|
143
|
+
zip.addFile('manifest.json', Buffer.from(JSON.stringify(manifest, null, 2)));
|
|
144
|
+
// Write ZIP file
|
|
145
|
+
zip.writeZip(outputPath);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Validate image format
|
|
149
|
+
*/
|
|
150
|
+
isValidImageFormat(format) {
|
|
151
|
+
return types_1.SUPPORTED_IMAGE_FORMATS.includes(format);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Validate image buffer by checking magic bytes
|
|
155
|
+
*/
|
|
156
|
+
isValidImageBuffer(buffer, format) {
|
|
157
|
+
if (buffer.length < 8)
|
|
158
|
+
return false;
|
|
159
|
+
switch (format.toLowerCase()) {
|
|
160
|
+
case 'png':
|
|
161
|
+
return buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47;
|
|
162
|
+
case 'jpg':
|
|
163
|
+
case 'jpeg':
|
|
164
|
+
return buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff;
|
|
165
|
+
case 'gif':
|
|
166
|
+
return buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46;
|
|
167
|
+
case 'webp':
|
|
168
|
+
return buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50;
|
|
169
|
+
case 'avif':
|
|
170
|
+
// AVIF detection is more complex, but we can check for 'ftyp' box
|
|
171
|
+
const ftypCheck = buffer.toString('ascii', 4, 8);
|
|
172
|
+
return ftypCheck === 'ftyp';
|
|
173
|
+
default:
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Get image dimensions (basic implementation for PNG and JPEG)
|
|
179
|
+
*/
|
|
180
|
+
getImageDimensions(buffer, format) {
|
|
181
|
+
try {
|
|
182
|
+
if (format === 'png' && buffer.length >= 24) {
|
|
183
|
+
const width = buffer.readUInt32BE(16);
|
|
184
|
+
const height = buffer.readUInt32BE(20);
|
|
185
|
+
return { width, height };
|
|
186
|
+
}
|
|
187
|
+
if ((format === 'jpg' || format === 'jpeg') && buffer.length >= 4) {
|
|
188
|
+
// Basic JPEG dimension extraction (simplified)
|
|
189
|
+
// This is a simplified version; production code might use a library
|
|
190
|
+
let offset = 2;
|
|
191
|
+
while (offset < buffer.length) {
|
|
192
|
+
if (buffer[offset] !== 0xff)
|
|
193
|
+
break;
|
|
194
|
+
const marker = buffer[offset + 1];
|
|
195
|
+
offset += 2;
|
|
196
|
+
if (marker === 0xc0 || marker === 0xc2) {
|
|
197
|
+
const height = buffer.readUInt16BE(offset + 3);
|
|
198
|
+
const width = buffer.readUInt16BE(offset + 5);
|
|
199
|
+
return { width, height };
|
|
200
|
+
}
|
|
201
|
+
const segmentLength = buffer.readUInt16BE(offset);
|
|
202
|
+
offset += segmentLength;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
// If dimension extraction fails, just return undefined
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get current image count
|
|
214
|
+
*/
|
|
215
|
+
getImageCount() {
|
|
216
|
+
return this.images.size;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Clear all images
|
|
220
|
+
*/
|
|
221
|
+
clear() {
|
|
222
|
+
this.images.clear();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.VREFWriter = VREFWriter;
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* VREF CLI - Command line interface for creating .vref files
|
|
5
|
+
* Harpia AI Research
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const VREFWriter_1 = require("./VREFWriter");
|
|
44
|
+
function showHelp() {
|
|
45
|
+
console.log(`
|
|
46
|
+
┌─────────────────────────────────────────────────┐
|
|
47
|
+
│ VREF CLI v1.0.0 │
|
|
48
|
+
│ by Harpia AI Research │
|
|
49
|
+
│ https://harpiaresearch.cc │
|
|
50
|
+
└─────────────────────────────────────────────────┘
|
|
51
|
+
|
|
52
|
+
Create .vref files from images
|
|
53
|
+
|
|
54
|
+
USAGE:
|
|
55
|
+
npx create-vref <input> [options]
|
|
56
|
+
|
|
57
|
+
ARGUMENTS:
|
|
58
|
+
<input> Path to image file or directory
|
|
59
|
+
|
|
60
|
+
OPTIONS:
|
|
61
|
+
-o, --output <path> Output .vref file path (default: output.vref)
|
|
62
|
+
-n, --name <name> Name for the reference pack
|
|
63
|
+
-d, --desc <text> Description of the reference pack
|
|
64
|
+
-t, --tags <tags> Comma-separated tags
|
|
65
|
+
-a, --author <name> Author name
|
|
66
|
+
-l, --license <lic> License (e.g., CC-BY-4.0, MIT)
|
|
67
|
+
-r, --recursive Include subdirectories
|
|
68
|
+
-h, --help Show this help
|
|
69
|
+
|
|
70
|
+
EXAMPLES:
|
|
71
|
+
# Create from single image
|
|
72
|
+
npx create-vref image.png -o references.vref
|
|
73
|
+
|
|
74
|
+
# Create from directory
|
|
75
|
+
npx create-vref ./images -o pack.vref -n "My Pack"
|
|
76
|
+
|
|
77
|
+
# With metadata
|
|
78
|
+
npx create-vref ./sprites -o rpg-sprites.vref \\
|
|
79
|
+
-n "RPG Sprites" \\
|
|
80
|
+
-d "Character sprites for RPG games" \\
|
|
81
|
+
-t "rpg,sprites,pixelart" \\
|
|
82
|
+
-a "Your Name" \\
|
|
83
|
+
-l "CC-BY-4.0"
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
86
|
+
function parseArgs(args) {
|
|
87
|
+
const options = {
|
|
88
|
+
input: null,
|
|
89
|
+
output: 'output.vref',
|
|
90
|
+
name: null,
|
|
91
|
+
description: null,
|
|
92
|
+
tags: null,
|
|
93
|
+
author: null,
|
|
94
|
+
license: null,
|
|
95
|
+
recursive: false,
|
|
96
|
+
};
|
|
97
|
+
for (let i = 0; i < args.length; i++) {
|
|
98
|
+
const arg = args[i];
|
|
99
|
+
switch (arg) {
|
|
100
|
+
case '-h':
|
|
101
|
+
case '--help':
|
|
102
|
+
showHelp();
|
|
103
|
+
process.exit(0);
|
|
104
|
+
break;
|
|
105
|
+
case '-o':
|
|
106
|
+
case '--output':
|
|
107
|
+
options.output = args[++i];
|
|
108
|
+
break;
|
|
109
|
+
case '-n':
|
|
110
|
+
case '--name':
|
|
111
|
+
options.name = args[++i];
|
|
112
|
+
break;
|
|
113
|
+
case '-d':
|
|
114
|
+
case '--desc':
|
|
115
|
+
case '--description':
|
|
116
|
+
options.description = args[++i];
|
|
117
|
+
break;
|
|
118
|
+
case '-t':
|
|
119
|
+
case '--tags':
|
|
120
|
+
options.tags = args[++i].split(',').map((t) => t.trim());
|
|
121
|
+
break;
|
|
122
|
+
case '-a':
|
|
123
|
+
case '--author':
|
|
124
|
+
options.author = args[++i];
|
|
125
|
+
break;
|
|
126
|
+
case '-l':
|
|
127
|
+
case '--license':
|
|
128
|
+
options.license = args[++i];
|
|
129
|
+
break;
|
|
130
|
+
case '-r':
|
|
131
|
+
case '--recursive':
|
|
132
|
+
options.recursive = true;
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
if (!arg.startsWith('-')) {
|
|
136
|
+
options.input = arg;
|
|
137
|
+
}
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return options;
|
|
142
|
+
}
|
|
143
|
+
function main() {
|
|
144
|
+
const args = process.argv.slice(2);
|
|
145
|
+
if (args.length === 0) {
|
|
146
|
+
showHelp();
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
const options = parseArgs(args);
|
|
150
|
+
if (!options.input) {
|
|
151
|
+
console.error('❌ Error: No input specified');
|
|
152
|
+
console.log('Run "npx create-vref --help" for usage information');
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
if (!fs.existsSync(options.input)) {
|
|
156
|
+
console.error(`❌ Error: Input not found: ${options.input}`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
console.log('┌─────────────────────────────────────────────────┐');
|
|
160
|
+
console.log('│ VREF CLI v1.0.0 │');
|
|
161
|
+
console.log('│ by Harpia AI Research │');
|
|
162
|
+
console.log('└─────────────────────────────────────────────────┘\n');
|
|
163
|
+
try {
|
|
164
|
+
const fileName = options.name || path.basename(options.output, '.vref');
|
|
165
|
+
const writer = new VREFWriter_1.VREFWriter({
|
|
166
|
+
fileName: options.output,
|
|
167
|
+
description: options.description,
|
|
168
|
+
tags: options.tags,
|
|
169
|
+
author: options.author,
|
|
170
|
+
license: options.license,
|
|
171
|
+
});
|
|
172
|
+
console.log(`📂 Input: ${options.input}`);
|
|
173
|
+
const stat = fs.statSync(options.input);
|
|
174
|
+
if (stat.isDirectory()) {
|
|
175
|
+
console.log(`🔍 Scanning directory${options.recursive ? ' (recursive)' : ''}...`);
|
|
176
|
+
writer.addImagesFromDirectory(options.input, options.recursive);
|
|
177
|
+
}
|
|
178
|
+
else if (stat.isFile()) {
|
|
179
|
+
console.log('📄 Adding image...');
|
|
180
|
+
writer.addImageFromFile(options.input);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
throw new Error('Input must be a file or directory');
|
|
184
|
+
}
|
|
185
|
+
const imageCount = writer.getImageCount();
|
|
186
|
+
if (imageCount === 0) {
|
|
187
|
+
console.error('❌ Error: No valid images found');
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
console.log(`✅ Found ${imageCount} valid image${imageCount !== 1 ? 's' : ''}`);
|
|
191
|
+
console.log(`📦 Creating ${options.output}...`);
|
|
192
|
+
writer.write(options.output);
|
|
193
|
+
const stats = fs.statSync(options.output);
|
|
194
|
+
const sizeMB = (stats.size / (1024 * 1024)).toFixed(2);
|
|
195
|
+
console.log(`✅ Success! Created ${options.output} (${sizeMB} MB)`);
|
|
196
|
+
console.log('\n📋 Metadata:');
|
|
197
|
+
console.log(` Images: ${imageCount}`);
|
|
198
|
+
if (options.name)
|
|
199
|
+
console.log(` Name: ${options.name}`);
|
|
200
|
+
if (options.description)
|
|
201
|
+
console.log(` Description: ${options.description}`);
|
|
202
|
+
if (options.tags)
|
|
203
|
+
console.log(` Tags: ${options.tags.join(', ')}`);
|
|
204
|
+
if (options.author)
|
|
205
|
+
console.log(` Author: ${options.author}`);
|
|
206
|
+
if (options.license)
|
|
207
|
+
console.log(` License: ${options.license}`);
|
|
208
|
+
console.log('\n🚀 Share this file with AI platforms that support VREF!');
|
|
209
|
+
console.log(' Learn more: https://harpiaresearch.cc\n');
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
console.error(`\n❌ Error: ${error.message}`);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
main();
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VREF (Visual Reference Format)
|
|
3
|
+
* Standardized container for curated visual references in AI image generation
|
|
4
|
+
*
|
|
5
|
+
* Harpia AI Research
|
|
6
|
+
* https://harpiaresearch.cc
|
|
7
|
+
*/
|
|
8
|
+
export { VREFWriter } from './VREFWriter';
|
|
9
|
+
export { VREFReader } from './VREFReader';
|
|
10
|
+
export { VREFManifest, VREFCreateOptions, VREFReadOptions, VREFReadResult, ImageMetadata, SupportedImageFormat, SUPPORTED_IMAGE_FORMATS, } from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* VREF (Visual Reference Format)
|
|
4
|
+
* Standardized container for curated visual references in AI image generation
|
|
5
|
+
*
|
|
6
|
+
* Harpia AI Research
|
|
7
|
+
* https://harpiaresearch.cc
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.SUPPORTED_IMAGE_FORMATS = exports.VREFReader = exports.VREFWriter = void 0;
|
|
11
|
+
var VREFWriter_1 = require("./VREFWriter");
|
|
12
|
+
Object.defineProperty(exports, "VREFWriter", { enumerable: true, get: function () { return VREFWriter_1.VREFWriter; } });
|
|
13
|
+
var VREFReader_1 = require("./VREFReader");
|
|
14
|
+
Object.defineProperty(exports, "VREFReader", { enumerable: true, get: function () { return VREFReader_1.VREFReader; } });
|
|
15
|
+
var types_1 = require("./types");
|
|
16
|
+
Object.defineProperty(exports, "SUPPORTED_IMAGE_FORMATS", { enumerable: true, get: function () { return types_1.SUPPORTED_IMAGE_FORMATS; } });
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VREF (Visual Reference Format) Type Definitions
|
|
3
|
+
* Harpia AI Research
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* VREF Manifest structure
|
|
7
|
+
* Automatically generated and included in every .vref file
|
|
8
|
+
*/
|
|
9
|
+
export interface VREFManifest {
|
|
10
|
+
/** VREF format version (semver) */
|
|
11
|
+
version: string;
|
|
12
|
+
/** Format identifier */
|
|
13
|
+
format: 'VREF';
|
|
14
|
+
/** ZIP compression version (if applicable) */
|
|
15
|
+
zipVersion?: string;
|
|
16
|
+
/** Name of the .vref file */
|
|
17
|
+
fileName: string;
|
|
18
|
+
/** Total number of images in the container */
|
|
19
|
+
imageCount: number;
|
|
20
|
+
/** Array of image metadata */
|
|
21
|
+
images: ImageMetadata[];
|
|
22
|
+
/** ISO 8601 timestamp of creation */
|
|
23
|
+
createdAt: string;
|
|
24
|
+
/** Optional metadata */
|
|
25
|
+
metadata?: {
|
|
26
|
+
/** Description of the reference pack */
|
|
27
|
+
description?: string;
|
|
28
|
+
/** Tags for categorization */
|
|
29
|
+
tags?: string[];
|
|
30
|
+
/** Author information */
|
|
31
|
+
author?: string;
|
|
32
|
+
/** License information */
|
|
33
|
+
license?: string;
|
|
34
|
+
/** Any additional custom fields */
|
|
35
|
+
[key: string]: any;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Metadata for individual images within the VREF
|
|
40
|
+
*/
|
|
41
|
+
export interface ImageMetadata {
|
|
42
|
+
/** Relative path within the .vref archive */
|
|
43
|
+
path: string;
|
|
44
|
+
/** Image format (png, jpg, jpeg, webp, gif, avif) */
|
|
45
|
+
format: string;
|
|
46
|
+
/** File size in bytes */
|
|
47
|
+
size: number;
|
|
48
|
+
/** Image width in pixels */
|
|
49
|
+
width?: number;
|
|
50
|
+
/** Image height in pixels */
|
|
51
|
+
height?: number;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Options for creating a VREF file
|
|
55
|
+
*/
|
|
56
|
+
export interface VREFCreateOptions {
|
|
57
|
+
/** Name for the output .vref file */
|
|
58
|
+
fileName: string;
|
|
59
|
+
/** Optional description */
|
|
60
|
+
description?: string;
|
|
61
|
+
/** Optional tags */
|
|
62
|
+
tags?: string[];
|
|
63
|
+
/** Optional author */
|
|
64
|
+
author?: string;
|
|
65
|
+
/** Optional license */
|
|
66
|
+
license?: string;
|
|
67
|
+
/** Additional metadata */
|
|
68
|
+
metadata?: Record<string, any>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Options for reading a VREF file
|
|
72
|
+
*/
|
|
73
|
+
export interface VREFReadOptions {
|
|
74
|
+
/** Whether to extract images to memory */
|
|
75
|
+
loadImages?: boolean;
|
|
76
|
+
/** Validate image formats */
|
|
77
|
+
validate?: boolean;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Result of reading a VREF file
|
|
81
|
+
*/
|
|
82
|
+
export interface VREFReadResult {
|
|
83
|
+
/** Parsed manifest */
|
|
84
|
+
manifest: VREFManifest;
|
|
85
|
+
/** Image buffers (if loadImages: true) */
|
|
86
|
+
images?: Map<string, Buffer>;
|
|
87
|
+
/** Get a random sample of N images */
|
|
88
|
+
sample: (count: number) => Buffer[];
|
|
89
|
+
/** Get specific image by path */
|
|
90
|
+
getImage: (path: string) => Buffer | undefined;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Supported image formats
|
|
94
|
+
*/
|
|
95
|
+
export declare const SUPPORTED_IMAGE_FORMATS: readonly ["png", "jpg", "jpeg", "webp", "gif", "avif"];
|
|
96
|
+
export type SupportedImageFormat = typeof SUPPORTED_IMAGE_FORMATS[number];
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* VREF (Visual Reference Format) Type Definitions
|
|
4
|
+
* Harpia AI Research
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.SUPPORTED_IMAGE_FORMATS = void 0;
|
|
8
|
+
/**
|
|
9
|
+
* Supported image formats
|
|
10
|
+
*/
|
|
11
|
+
exports.SUPPORTED_IMAGE_FORMATS = [
|
|
12
|
+
'png',
|
|
13
|
+
'jpg',
|
|
14
|
+
'jpeg',
|
|
15
|
+
'webp',
|
|
16
|
+
'gif',
|
|
17
|
+
'avif'
|
|
18
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harpiaresearch/vref",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Visual Reference Format - Standardized container for curated visual references in AI image generation",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist/**/*",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
12
|
+
"bin": {
|
|
13
|
+
"create-vref": "./dist/cli.js",
|
|
14
|
+
"vref": "./dist/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"test": "node --test",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"vref",
|
|
24
|
+
"visual-reference",
|
|
25
|
+
"ai",
|
|
26
|
+
"image-generation",
|
|
27
|
+
"midjourney",
|
|
28
|
+
"stable-diffusion",
|
|
29
|
+
"harpia"
|
|
30
|
+
],
|
|
31
|
+
"author": "Harpia AI Research",
|
|
32
|
+
"license": "BSD-3-Clause",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/Harpia-AI-Research/vref"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"adm-zip": "^0.5.10"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/adm-zip": "^0.5.5",
|
|
42
|
+
"@types/node": "^20.0.0",
|
|
43
|
+
"typescript": "^5.3.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18.0.0"
|
|
47
|
+
}
|
|
48
|
+
}
|