@h3l1os/mp4vault 2.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 +661 -0
- package/README.md +228 -0
- package/dist/index.cjs +1525 -0
- package/dist/index.d.cts +261 -0
- package/dist/index.d.ts +261 -0
- package/dist/index.js +1479 -0
- package/package.json +69 -0
- package/src/AES.ts +134 -0
- package/src/Atom.ts +148 -0
- package/src/Convert.ts +28 -0
- package/src/Embed.ts +243 -0
- package/src/EmbedBinary.ts +204 -0
- package/src/EmbedObject.ts +231 -0
- package/src/MP4.ts +363 -0
- package/src/Pack.ts +11 -0
- package/src/constants.ts +3 -0
- package/src/index.ts +11 -0
- package/src/jspack.d.ts +10 -0
- package/src/jspack.js +319 -0
- package/src/node/Readable.ts +61 -0
- package/src/node/Writable.ts +85 -0
- package/src/types.ts +44 -0
- package/src/utils.ts +6 -0
package/README.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# @h3l1os/mp4vault
|
|
2
|
+
|
|
3
|
+
Hide and extract files within MP4 video containers with optional AES-256-GCM encryption.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Embed any file (text, images, binaries) inside an MP4 container
|
|
8
|
+
- AES-256-GCM authenticated encryption with key or password
|
|
9
|
+
- Mix public and encrypted files in the same container
|
|
10
|
+
- Attach metadata to embedded files
|
|
11
|
+
- Preserves MP4 playability — output files remain valid MP4 videos
|
|
12
|
+
- Dual ESM/CJS output
|
|
13
|
+
- Node.js 18+
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @h3l1os/mp4vault
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
### Embed a file with key encryption
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { MP4, Convert, Writable } from '@h3l1os/mp4vault';
|
|
27
|
+
|
|
28
|
+
const key = Convert.hexStringToBuffer('000102030405060708090a0b0c0d0e0f');
|
|
29
|
+
|
|
30
|
+
const mp4 = new MP4();
|
|
31
|
+
mp4.setKey(key);
|
|
32
|
+
await mp4.loadFile({ filename: 'video.mp4' });
|
|
33
|
+
|
|
34
|
+
await mp4.embedFile({ filename: 'secret.pdf' });
|
|
35
|
+
|
|
36
|
+
const writable = new Writable({ filename: 'output.mp4' });
|
|
37
|
+
await mp4.embed(writable);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Embed a file with password encryption
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const mp4 = new MP4();
|
|
44
|
+
mp4.setPassword('my-secret-password');
|
|
45
|
+
await mp4.loadFile({ filename: 'video.mp4' });
|
|
46
|
+
|
|
47
|
+
await mp4.embedFile({ filename: 'secret.pdf' });
|
|
48
|
+
|
|
49
|
+
const writable = new Writable({ filename: 'output.mp4' });
|
|
50
|
+
await mp4.embed(writable);
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Embed without encryption
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
const mp4 = new MP4();
|
|
57
|
+
await mp4.loadFile({ filename: 'video.mp4' });
|
|
58
|
+
|
|
59
|
+
await mp4.embedFile({ filename: 'document.txt' });
|
|
60
|
+
|
|
61
|
+
const writable = new Writable({ filename: 'output.mp4' });
|
|
62
|
+
await mp4.embed(writable);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Mix public and encrypted files
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const mp4 = new MP4();
|
|
69
|
+
mp4.setKey(key);
|
|
70
|
+
await mp4.loadFile({ filename: 'video.mp4' });
|
|
71
|
+
|
|
72
|
+
// Public file (opt out of encryption with password: null)
|
|
73
|
+
await mp4.embedFile({ filename: 'readme.txt', password: null });
|
|
74
|
+
|
|
75
|
+
// Encrypted file (uses the key set on the MP4 instance)
|
|
76
|
+
await mp4.embedFile({ filename: 'secret.pdf' });
|
|
77
|
+
|
|
78
|
+
const writable = new Writable({ filename: 'output.mp4' });
|
|
79
|
+
await mp4.embed(writable);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Embed with metadata
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
await mp4.embedFile({
|
|
86
|
+
filename: 'photo.jpg',
|
|
87
|
+
meta: { author: 'alice', tags: ['vacation', '2026'] },
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Extract files
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { MP4, Convert, Writable } from '@h3l1os/mp4vault';
|
|
95
|
+
|
|
96
|
+
const key = Convert.hexStringToBuffer('000102030405060708090a0b0c0d0e0f');
|
|
97
|
+
|
|
98
|
+
const mp4 = new MP4();
|
|
99
|
+
mp4.setKey(key);
|
|
100
|
+
await mp4.loadFile({ filename: 'output.mp4' });
|
|
101
|
+
|
|
102
|
+
// List embedded files
|
|
103
|
+
const files = mp4.getEmbedFiles();
|
|
104
|
+
console.log(files);
|
|
105
|
+
// [{ filename: 'secret.pdf', size: 12345, isEncrypted: true, offset: 0 }]
|
|
106
|
+
|
|
107
|
+
// Extract by index
|
|
108
|
+
const extracted = await mp4.extractFile(0);
|
|
109
|
+
await (extracted as Writable).saveToFile('restored-secret.pdf');
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Get expected output size
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
const mp4 = new MP4();
|
|
116
|
+
mp4.setKey(key);
|
|
117
|
+
await mp4.loadFile({ filename: 'video.mp4' });
|
|
118
|
+
await mp4.embedFile({ filename: 'secret.pdf' });
|
|
119
|
+
|
|
120
|
+
const expectedBytes = await mp4.getExpectedSize();
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## API
|
|
124
|
+
|
|
125
|
+
### `MP4`
|
|
126
|
+
|
|
127
|
+
| Method | Description |
|
|
128
|
+
|--------|-------------|
|
|
129
|
+
| `setKey(key: Buffer)` | Set AES encryption key (16, 24, or 32 bytes) |
|
|
130
|
+
| `setPassword(password: string)` | Set password for PBKDF2 key derivation |
|
|
131
|
+
| `loadFile({ filename })` | Parse an MP4 file |
|
|
132
|
+
| `embedFile({ filename, meta?, key?, password? })` | Add a file to embed. Pass `password: null` or `key: null` to opt out of encryption for this file |
|
|
133
|
+
| `embed(writable?)` | Write the MP4 with embedded data |
|
|
134
|
+
| `getExpectedSize()` | Get the expected output size in bytes |
|
|
135
|
+
| `getEmbedFiles()` | List files embedded in the loaded MP4 |
|
|
136
|
+
| `extractFile(index, writable?)` | Extract an embedded file by index |
|
|
137
|
+
| `findAtom(name)` | Find a top-level atom by name |
|
|
138
|
+
| `findAtoms(atoms, name)` | Recursively find atoms by name |
|
|
139
|
+
|
|
140
|
+
### `Convert`
|
|
141
|
+
|
|
142
|
+
| Method | Description |
|
|
143
|
+
|--------|-------------|
|
|
144
|
+
| `hexStringToBuffer(hex)` | Convert a hex string to a Buffer (validates input) |
|
|
145
|
+
| `objectToBuffer(obj)` | Serialize an object to a Buffer (JSON) |
|
|
146
|
+
| `bufferToObject(buf)` | Deserialize a Buffer to an object |
|
|
147
|
+
|
|
148
|
+
### `Writable`
|
|
149
|
+
|
|
150
|
+
| Method | Description |
|
|
151
|
+
|--------|-------------|
|
|
152
|
+
| `new Writable({ filename? })` | Create a writable (file or in-memory) |
|
|
153
|
+
| `saveToFile(filename)` | Save in-memory contents to a file |
|
|
154
|
+
| `toReadable()` | Convert to a Readable for re-processing |
|
|
155
|
+
| `size()` | Get bytes written |
|
|
156
|
+
|
|
157
|
+
## Security
|
|
158
|
+
|
|
159
|
+
- **AES-256-GCM** authenticated encryption (detects tampering)
|
|
160
|
+
- **PBKDF2** key derivation with 600,000 iterations and SHA-512 for password-based encryption
|
|
161
|
+
- Random 12-byte IV and 16-byte salt per encryption operation via `crypto.randomBytes()`
|
|
162
|
+
- Uses Node.js native `crypto` module — no third-party crypto dependencies
|
|
163
|
+
|
|
164
|
+
## Binary format
|
|
165
|
+
|
|
166
|
+
Embedded data is placed at the start of the `mdat` atom payload:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
[public header][encrypted header][file1 data][file2 data]...
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Each header and file follows this format:
|
|
173
|
+
|
|
174
|
+
- **Unencrypted**: `[flag 1B][type 1B][payload]`
|
|
175
|
+
- **Encrypted**: `[flag 1B][type 1B][salt 16B][IV 12B][ciphertext][authTag 16B]`
|
|
176
|
+
|
|
177
|
+
Sample offsets in `stco`/`co64` atoms are adjusted to account for the inserted data.
|
|
178
|
+
|
|
179
|
+
## Development
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
npm install
|
|
183
|
+
npm run build # Build ESM + CJS with tsup
|
|
184
|
+
npm run typecheck # TypeScript strict mode check
|
|
185
|
+
npm test # Run all tests with vitest
|
|
186
|
+
npm run test:watch # Watch mode
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Test suite
|
|
190
|
+
|
|
191
|
+
- **32 tests** across 7 test files
|
|
192
|
+
- Unit tests: AES encryption, Pack binary operations, Convert utilities
|
|
193
|
+
- Integration tests: EmbedBinary, EmbedObject, EmbedMeta
|
|
194
|
+
- End-to-end tests: full embed/extract cycles with key, password, no encryption, mixed files, binary files, metadata, re-embedding, size validation, wrong key rejection
|
|
195
|
+
|
|
196
|
+
## Project structure
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
src/
|
|
200
|
+
index.ts # Public API exports
|
|
201
|
+
MP4.ts # MP4 parser, embedder, extractor
|
|
202
|
+
Atom.ts # MP4 atom/box representation
|
|
203
|
+
AES.ts # AES-256-GCM encryption
|
|
204
|
+
Embed.ts # Embedding coordinator
|
|
205
|
+
EmbedBinary.ts # Binary file embedding
|
|
206
|
+
EmbedObject.ts # JSON object embedding
|
|
207
|
+
Convert.ts # Buffer/hex/JSON utilities
|
|
208
|
+
Pack.ts # Binary pack/unpack (struct encoding)
|
|
209
|
+
jspack.js # Vendored jspack library
|
|
210
|
+
constants.ts # Buffer size, max header, max int32
|
|
211
|
+
types.ts # IReadable, IWritable, FileRecord interfaces
|
|
212
|
+
utils.ts # Temp file helper
|
|
213
|
+
node/
|
|
214
|
+
Readable.ts # Node.js file reader
|
|
215
|
+
Writable.ts # Node.js file writer
|
|
216
|
+
test/
|
|
217
|
+
common.test.ts # AES, Convert, Pack unit tests
|
|
218
|
+
embedBinary.test.ts
|
|
219
|
+
embedObject.test.ts
|
|
220
|
+
embedMeta.test.ts
|
|
221
|
+
mixed.test.ts
|
|
222
|
+
mp4.test.ts
|
|
223
|
+
e2e.test.ts # End-to-end embed/extract tests
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## License
|
|
227
|
+
|
|
228
|
+
[AGPL-3.0](LICENSE)
|