shifro 0.2.0 → 1.1.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/README.md CHANGED
@@ -1,155 +1,99 @@
1
1
  # shifro
2
2
 
3
- A lightweight library for decrypting MP4 files, directly in the browser
3
+ A lightweight library for decrypting MP4 files, directly in the browser or via CLI on your machine.
4
4
 
5
- ## Features
5
+ This tool is a small wrapper around the [Mediabunny](https://github.com/Vanilagy/mediabunny) library with slight API changes and command-line support.
6
6
 
7
- - **Decryption** of MP4 files with MPEG Common Encryption (CENC)
8
- - **Small** size (under 15kB without types, minified)
9
- - **Command-line** interface
10
- - **Segment-by-segment processing** with JavaScript library
11
- - **Custom handler** for samples processing
12
-
13
- ## Prerequisites
14
-
15
- - [Node.js](https://nodejs.org/en)
16
-
17
- ## Installation
18
-
19
- Install library as dependency for your project:
7
+ ## Usage
20
8
 
21
- ```bash
22
- npm install shifro
23
- ```
9
+ ### CLI
24
10
 
25
- Install globally to use as command-line tool:
11
+ Run `npm install -g shifro` to install the command-line tool globally ([Node.js](https://nodejs.org/en/download) required).
26
12
 
27
13
  ```bash
28
- npm install -g shifro
14
+ shifro --key eb676abbcb345e96bbcf616630f1a3da:100b6c20940f779a4589152b57d2dacb ./input.mp4 ./output.mp4
29
15
  ```
30
16
 
31
- ## Usage
17
+ This can be used as an alternative to the [mp4decrypt](https://www.bento4.com/documentation/mp4decrypt/) or [shaka-packager](https://shaka-project.github.io/shaka-packager/html/documentation.html#raw-key-encryption-options).
32
18
 
33
19
  ### Library
34
20
 
35
- #### Decrypting file using browser's Web Streams API
36
-
37
- ```js
38
- import { Input, StreamSource, Output, StreamTarget, Decryption } from 'shifro';
21
+ Run `npm install shifro` to install the library as dependency for your project.
39
22
 
40
- const selectFile = async () =>
41
- new Promise<File>((resolve) => {
42
- const input = document.querySelector<HTMLInputElement>('#input')!;
43
- input.addEventListener('change', () => resolve(input.files![0]));
44
- });
45
-
46
- async function main() {
47
- const inputFile = await selectFile();
48
- const inputStream = inputFile.stream();
23
+ #### Decrypting file using Node.js streams
49
24
 
50
- const outputFileHandle = window.showSaveFilePicker({ suggestedName: 'output.mp4', startIn: 'downloads' });
51
- const outputStream = await outputFileHandle.createWritable();
25
+ ```ts
26
+ import { Decryption, FilePathSource, FilePathTarget, Input, Output } from 'shifro';
27
+ import type { Key, KeyId } from 'shifro';
52
28
 
53
- const input = new Input({ source: new StreamSource(inputStream) });
54
- const output = new Output({ target: new StreamTarget(outputStream) });
29
+ async function decrypt() {
55
30
  const decryption = await Decryption.init({
56
- input,
57
- output,
58
- keys: [
59
- {
60
- kid: 'eb676abbcb345e96bbcf616630f1a3da',
61
- key: '100b6c20940f779a4589152b57d2dacb',
31
+ input: new Input({
32
+ source: new FilePathSource('./input.mp4'),
33
+ keys: new Map<KeyId, Key>([
34
+ ['4d97930a3d7b55fa81d0028653f5e499', '429ec76475e7a952d224d8ef867f12b6'],
35
+ ['d21373c0b8ab5ba9954742bcdfb5f48b', '150a6c7d7dee6a91b74dccfce5b31928'],
36
+ ]),
37
+ // Optinally, you can use this callback to handle encryption information (like key ID and PSSH boxes)
38
+ handleEncryptionInfo: ({ keyId, psshBoxes }) => {
39
+ console.group(`Key ID: ${keyId}`);
40
+ for (const psshBox of psshBoxes) {
41
+ console.log(`PSSH box: ${psshBox.systemId} (${psshBox.data.length} bytes)`);
42
+ }
43
+ console.groupEnd();
62
44
  },
63
- ],
64
- onProgress: (progress) => console.log(`Decrypting... ${progress}`),
45
+ }),
46
+ output: new Output({ target: new FilePathTarget('./output.mp4') }),
65
47
  });
66
- await decryption.execute();
67
- }
68
- ```
69
48
 
70
- #### Decrypting file using Node.js streams
49
+ decryption.onProgress = (progress) =>
50
+ process.stdout.write(`\rDecrypting... [${Math.round(progress * 100)}%]`);
71
51
 
72
- ```js
73
- import { Input, FilePathSource, Output, FilePathTarget, Decryption } from 'shifro';
74
-
75
- async function main() {
76
- const input = new Input({ source: new FilePathSource('./input.mp4') });
77
- const output = new Output({ target: new FilePathTarget('./output.mp4') });
78
- const decryption = await Decryption.init({
79
- input,
80
- output,
81
- keys: [
82
- {
83
- kid: 'eb676abbcb345e96bbcf616630f1a3da',
84
- key: '100b6c20940f779a4589152b57d2dacb',
85
- },
86
- ],
87
- onProgress: (progress) => process.stdout.write(`\rDecrypting... [${progress}]`),
88
- });
89
52
  await decryption.execute();
90
53
  }
91
54
  ```
92
55
 
93
- #### Segment-by-segment decryption
94
-
95
- ```js
96
- import { decryptSegment } from 'shifro';
56
+ #### Decrypting file using browser's Web Streams API
97
57
 
98
- const key = 'eb676abbcb345e96bbcf616630f1a3da';
99
- const keyId = '100b6c20940f779a4589152b57d2dacb';
100
- const encryptionScheme = 'cenc';
58
+ ```ts
59
+ import { Decryption, Input, Output, ReadableStreamSource, StreamTarget } from 'shifro';
60
+ import type { Key, KeyId } from 'shifro';
101
61
 
102
- const encryptedSegments = [
103
- // List of binary MP4 segments (Uint8Array)
104
- // ...
105
- ];
106
- const decryptedSegments = [];
62
+ async function decryptFromBrowser() {
63
+ const handleFileSelect = async () => {
64
+ return new Promise<File>((resolve) => {
65
+ const input = document.querySelector<HTMLInputElement>('#input')!;
66
+ input.addEventListener('change', () => resolve(input.files![0]));
67
+ });
68
+ };
107
69
 
108
- for (const segment of encryptedSegments) {
109
- const decrypted = await decryptSegment(segment, { key, keyId, encryptionScheme });
110
- decryptedSegments.push(decrypted);
111
- }
70
+ const inputFile = await handleFileSelect();
71
+ const inputReadableStream = inputFile.stream();
112
72
 
113
- // Do something with results...
114
- ```
73
+ const outputFileHandle = window.showSaveFilePicker({
74
+ suggestedName: 'output.mp4',
75
+ startIn: 'downloads',
76
+ });
77
+ const outputWritableStream = await outputFileHandle.createWritable();
115
78
 
116
- #### Transform sample data in your own way instead of built-in decryption
117
-
118
- ```js
119
- import { createDecipheriv } from 'node:crypto';
120
- import { decryptSegment } from 'shifro';
121
-
122
- const key = 'eb676abbcb345e96bbcf616630f1a3da';
123
- const keyId = '100b6c20940f779a4589152b57d2dacb';
124
- const encryptionScheme = 'cenc';
125
-
126
- const encryptedSegments = [
127
- // List of binary MP4 segments (Uint8Array)
128
- // ...
129
- ];
130
- const decryptedSegments = [];
131
-
132
- for (const segment of encryptedSegments) {
133
- const decrypted = await decryptSegment(segment, {
134
- keyId,
135
- transformSample: (params) => {
136
- // Implement custom handler for subsample processing
137
- // ...
138
- const sampleData = params.data;
139
- const iv = params.iv;
140
- const decipher = createDecipheriv('aes-128-ctr', key, iv);
141
- const decrypted = Buffer.concat([decipher.update(sampleData), decipher.final()]);
142
- return decrypted;
143
- },
79
+ const decryption = await Decryption.init({
80
+ input: new Input({
81
+ source: new ReadableStreamSource(inputReadableStream),
82
+ keys: new Map<KeyId, Key>([
83
+ ['4d97930a3d7b55fa81d0028653f5e499', '429ec76475e7a952d224d8ef867f12b6'],
84
+ ['d21373c0b8ab5ba9954742bcdfb5f48b', '150a6c7d7dee6a91b74dccfce5b31928'],
85
+ ]),
86
+ }),
87
+ output: new Output({ target: new StreamTarget(outputWritableStream) }),
144
88
  });
145
- decryptedSegments.push(decrypted);
146
- }
147
89
 
148
- // Do something with results...
90
+ decryption.onProgress = (progress) =>
91
+ console.log(`Decrypting... [${Math.round(progress * 100)}%]`);
92
+
93
+ await decryption.execute();
94
+ }
149
95
  ```
150
96
 
151
- ### CLI
97
+ ## Credits
152
98
 
153
- ```bash
154
- shifro --key eb676abbcb345e96bbcf616630f1a3da:100b6c20940f779a4589152b57d2dacb ./input.mp4 ./output.mp4
155
- ```
99
+ - [mediabunny](https://github.com/Vanilagy/mediabunny)
package/dist/cli.mjs ADDED
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from "node:util";
3
+ import { ALL_FORMATS, Conversion, FilePathSource, FilePathTarget, Input, Mp4OutputFormat, Output } from "mediabunny";
4
+ //#region package.json
5
+ var name = "shifro";
6
+ var version = "1.1.0";
7
+ var author = "Vitaly Gashkov <vitalygashkov@vk.com>";
8
+ //#endregion
9
+ //#region src/index.ts
10
+ var Input$1 = class extends Input {
11
+ constructor({ keys, handleEncryptionInfo, ...options }) {
12
+ super({
13
+ ...options,
14
+ formats: ALL_FORMATS,
15
+ formatOptions: { isobmff: { resolveKeyId: ({ keyId, psshBoxes }) => {
16
+ handleEncryptionInfo?.({
17
+ keyId,
18
+ psshBoxes
19
+ });
20
+ const key = keys.get(keyId);
21
+ if (!key) throw new Error("Unknown key ID.");
22
+ return key;
23
+ } } }
24
+ });
25
+ }
26
+ };
27
+ var Output$1 = class extends Output {
28
+ constructor({ ...options }) {
29
+ super({
30
+ ...options,
31
+ format: options.format ?? new Mp4OutputFormat()
32
+ });
33
+ }
34
+ };
35
+ //#endregion
36
+ //#region src/cli.ts
37
+ const args = parseArgs({
38
+ allowPositionals: true,
39
+ strict: false,
40
+ options: {
41
+ key: {
42
+ short: "k",
43
+ type: "string",
44
+ multiple: true
45
+ },
46
+ help: {
47
+ short: "h",
48
+ type: "boolean"
49
+ }
50
+ }
51
+ });
52
+ const keyStrings = Array.isArray(args.values.key) ? args.values.key : [];
53
+ const [inputPath, outputPath] = args.positionals;
54
+ const decryptFile = async () => {
55
+ console.time("\nDone!");
56
+ const decryption = await Conversion.init({
57
+ input: new Input$1({
58
+ source: new FilePathSource(inputPath),
59
+ keys: new Map(keyStrings.map((keyString) => keyString.split(":")))
60
+ }),
61
+ output: new Output$1({ target: new FilePathTarget(outputPath) })
62
+ });
63
+ decryption.onProgress = (progress) => process.stdout.write(`\rDecrypting... [${Math.round(progress * 100)}%]`);
64
+ await decryption.execute();
65
+ console.timeEnd("\nDone!");
66
+ };
67
+ const showHelp = () => {
68
+ console.log(`
69
+ ${name} version ${version}
70
+ (c) 2024-2026 ${author}
71
+
72
+ Usage: ${name} [options] <input> <output>
73
+
74
+ Options:
75
+ --key <id>:<k>
76
+ <id> is either a track ID in decimal or a 128-bit KID in hex,
77
+ <k> is a 128-bit key in hex
78
+ (several --key options can be used, one for each track or KID)
79
+ `.trim());
80
+ };
81
+ (async () => {
82
+ if (inputPath && outputPath && keyStrings.length > 0) await decryptFile();
83
+ else showHelp();
84
+ })();
85
+ //#endregion
86
+ export {};
package/dist/index.cjs ADDED
@@ -0,0 +1,42 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let mediabunny = require("mediabunny");
3
+ //#region src/index.ts
4
+ var Input = class extends mediabunny.Input {
5
+ constructor({ keys, handleEncryptionInfo, ...options }) {
6
+ super({
7
+ ...options,
8
+ formats: mediabunny.ALL_FORMATS,
9
+ formatOptions: { isobmff: { resolveKeyId: ({ keyId, psshBoxes }) => {
10
+ handleEncryptionInfo?.({
11
+ keyId,
12
+ psshBoxes
13
+ });
14
+ const key = keys.get(keyId);
15
+ if (!key) throw new Error("Unknown key ID.");
16
+ return key;
17
+ } } }
18
+ });
19
+ }
20
+ };
21
+ var Output = class extends mediabunny.Output {
22
+ constructor({ ...options }) {
23
+ super({
24
+ ...options,
25
+ format: options.format ?? new mediabunny.Mp4OutputFormat()
26
+ });
27
+ }
28
+ };
29
+ //#endregion
30
+ exports.Decryption = mediabunny.Conversion;
31
+ exports.FilePathSource = mediabunny.FilePathSource;
32
+ exports.FilePathTarget = mediabunny.FilePathTarget;
33
+ exports.Input = Input;
34
+ exports.MkvOutputFormat = mediabunny.MkvOutputFormat;
35
+ exports.MovOutputFormat = mediabunny.MovOutputFormat;
36
+ exports.Mp3OutputFormat = mediabunny.Mp3OutputFormat;
37
+ exports.Mp4OutputFormat = mediabunny.Mp4OutputFormat;
38
+ exports.Output = Output;
39
+ exports.ReadableStreamSource = mediabunny.ReadableStreamSource;
40
+ exports.StreamSource = mediabunny.StreamSource;
41
+ exports.StreamTarget = mediabunny.StreamTarget;
42
+ exports.UrlSource = mediabunny.UrlSource;
@@ -0,0 +1,28 @@
1
+ import { Conversion, FilePathSource, FilePathTarget, Input as Input$1, InputOptions, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output as Output$1, OutputFormat, OutputOptions, PsshBox, ReadableStreamSource, StreamSource, StreamSourceOptions, StreamTarget, StreamTargetOptions, UrlSource } from "mediabunny";
2
+
3
+ //#region src/index.d.ts
4
+ type KeyId = string;
5
+ type Key = string;
6
+ type KeyMap = Map<KeyId, Key>;
7
+ declare class Input extends Input$1 {
8
+ constructor({
9
+ keys,
10
+ handleEncryptionInfo,
11
+ ...options
12
+ }: Omit<InputOptions, 'formats' | 'formatOptions'> & {
13
+ keys: KeyMap;
14
+ handleEncryptionInfo?: (options: {
15
+ keyId: string;
16
+ psshBoxes: PsshBox[];
17
+ }) => void;
18
+ });
19
+ }
20
+ declare class Output extends Output$1 {
21
+ constructor({
22
+ ...options
23
+ }: Omit<OutputOptions, 'format'> & {
24
+ format?: OutputFormat;
25
+ });
26
+ }
27
+ //#endregion
28
+ export { Conversion as Decryption, FilePathSource, FilePathTarget, Input, type InputOptions, type Key, type KeyId, type KeyMap, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output, type OutputFormat, type OutputOptions, ReadableStreamSource, StreamSource, type StreamSourceOptions, StreamTarget, type StreamTargetOptions, UrlSource };
@@ -0,0 +1,28 @@
1
+ import { Conversion, FilePathSource, FilePathTarget, Input as Input$1, InputOptions, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output as Output$1, OutputFormat, OutputOptions, PsshBox, ReadableStreamSource, StreamSource, StreamSourceOptions, StreamTarget, StreamTargetOptions, UrlSource } from "mediabunny";
2
+
3
+ //#region src/index.d.ts
4
+ type KeyId = string;
5
+ type Key = string;
6
+ type KeyMap = Map<KeyId, Key>;
7
+ declare class Input extends Input$1 {
8
+ constructor({
9
+ keys,
10
+ handleEncryptionInfo,
11
+ ...options
12
+ }: Omit<InputOptions, 'formats' | 'formatOptions'> & {
13
+ keys: KeyMap;
14
+ handleEncryptionInfo?: (options: {
15
+ keyId: string;
16
+ psshBoxes: PsshBox[];
17
+ }) => void;
18
+ });
19
+ }
20
+ declare class Output extends Output$1 {
21
+ constructor({
22
+ ...options
23
+ }: Omit<OutputOptions, 'format'> & {
24
+ format?: OutputFormat;
25
+ });
26
+ }
27
+ //#endregion
28
+ export { Conversion as Decryption, FilePathSource, FilePathTarget, Input, type InputOptions, type Key, type KeyId, type KeyMap, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output, type OutputFormat, type OutputOptions, ReadableStreamSource, StreamSource, type StreamSourceOptions, StreamTarget, type StreamTargetOptions, UrlSource };
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ import { ALL_FORMATS, Conversion, FilePathSource, FilePathTarget, Input as Input$1, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output as Output$1, ReadableStreamSource, StreamSource, StreamTarget, UrlSource } from "mediabunny";
2
+ //#region src/index.ts
3
+ var Input = class extends Input$1 {
4
+ constructor({ keys, handleEncryptionInfo, ...options }) {
5
+ super({
6
+ ...options,
7
+ formats: ALL_FORMATS,
8
+ formatOptions: { isobmff: { resolveKeyId: ({ keyId, psshBoxes }) => {
9
+ handleEncryptionInfo?.({
10
+ keyId,
11
+ psshBoxes
12
+ });
13
+ const key = keys.get(keyId);
14
+ if (!key) throw new Error("Unknown key ID.");
15
+ return key;
16
+ } } }
17
+ });
18
+ }
19
+ };
20
+ var Output = class extends Output$1 {
21
+ constructor({ ...options }) {
22
+ super({
23
+ ...options,
24
+ format: options.format ?? new Mp4OutputFormat()
25
+ });
26
+ }
27
+ };
28
+ //#endregion
29
+ export { Conversion as Decryption, FilePathSource, FilePathTarget, Input, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output, ReadableStreamSource, StreamSource, StreamTarget, UrlSource };
package/package.json CHANGED
@@ -1,41 +1,31 @@
1
1
  {
2
2
  "name": "shifro",
3
- "version": "0.2.0",
3
+ "version": "1.1.0",
4
4
  "description": "A lightweight library for decrypting MP4 files, directly in the browser",
5
5
  "files": [
6
6
  "dist"
7
7
  ],
8
- "scripts": {
9
- "start": "node ./dist/cli.cjs",
10
- "web": "vite",
11
- "build": "npm run build:lib && npm run build:cli",
12
- "build:cli": "tsdown ./lib/node/cli.ts --format cjs --dts --minify --no-clean",
13
- "build:lib": "tsdown shifro.ts --format cjs --format esm --dts --platform neutral",
14
- "typecheck": "tsc --noEmit",
15
- "test": "vitest",
16
- "prepublishOnly": "npm run build"
17
- },
18
8
  "bin": {
19
- "shifro": "./dist/cli.cjs"
9
+ "shifro": "./dist/cli.mjs"
20
10
  },
21
11
  "type": "module",
22
- "main": "./dist/shifro.js",
23
- "types": "./dist/shifro.d.ts",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
24
14
  "exports": {
25
15
  ".": {
26
16
  "require": {
27
- "types": "./dist/shifro.d.cts",
28
- "default": "./dist/shifro.cjs"
17
+ "types": "./dist/index.d.cts",
18
+ "default": "./dist/index.cjs"
29
19
  },
30
20
  "import": {
31
- "types": "./dist/shifro.d.ts",
32
- "default": "./dist/shifro.js"
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
33
23
  }
34
24
  }
35
25
  },
36
26
  "repository": {
37
27
  "type": "git",
38
- "url": "git+https://github.com/streamyx-labs/shifro.git"
28
+ "url": "git+https://github.com/azot-labs/shifro.git"
39
29
  },
40
30
  "keywords": [
41
31
  "mp4",
@@ -54,11 +44,11 @@
54
44
  "shaka-packager"
55
45
  ],
56
46
  "bugs": {
57
- "url": "https://github.com/streamyx-labs/shifro/issues",
47
+ "url": "https://github.com/azot-labs/shifro/issues",
58
48
  "email": "vitalygashkov@vk.com"
59
49
  },
60
50
  "author": "Vitaly Gashkov <vitalygashkov@vk.com>",
61
- "license": "AGPL-3.0",
51
+ "license": "MIT",
62
52
  "readmeFilename": "README.md",
63
53
  "funding": [
64
54
  {
@@ -67,15 +57,34 @@
67
57
  }
68
58
  ],
69
59
  "engines": {
70
- "node": ">=20"
60
+ "node": ">=22"
61
+ },
62
+ "dependencies": {
63
+ "mediabunny": "^1.42.0-beta.7"
71
64
  },
72
65
  "devDependencies": {
73
66
  "@types/node": "^24.5.2",
74
- "tsdown": "^0.15.5",
75
- "typescript": "^5.9.2",
76
- "vitest": "^3.2.4"
67
+ "oxfmt": "^0.46.0",
68
+ "oxlint": "^1.61.0",
69
+ "tsdown": "^0.21.10",
70
+ "typescript": "~6.0.3",
71
+ "vitest": "^4.1.5"
77
72
  },
78
- "dependencies": {
79
- "mp4box": "^2.1.1"
73
+ "scripts": {
74
+ "start": "node ./dist/cli.mjs",
75
+ "web": "vite",
76
+ "build": "pnpm run bundle:lib && pnpm run bundle:cli",
77
+ "bundle:lib": "tsdown ./src/index.ts --format cjs --format esm --dts --platform neutral",
78
+ "bundle:cli": "tsdown ./src/cli.ts --format esm --no-clean",
79
+ "build:cli": "pnpm run build:cli:mac && pnpm run build:cli:linux && pnpm run build:cli:win",
80
+ "build:cli:mac": "bun build --compile --minify --bytecode --target=bun-darwin-arm64 ./src/cli.ts --outfile shifro-windows.exe",
81
+ "build:cli:linux": "bun build --compile --minify --bytecode --target=bun-linux-x64 ./src/cli.ts --outfile shifro-linux",
82
+ "build:cli:win": "bun build --compile --minify --bytecode --target=bun-windows-x64 ./src/cli.ts --outfile shifro-macos",
83
+ "fmt": "oxfmt .",
84
+ "fmt:check": "oxfmt --check .",
85
+ "lint": "oxlint .",
86
+ "lint:fix": "oxlint --fix .",
87
+ "typecheck": "tsc --noEmit",
88
+ "test": "vitest"
80
89
  }
81
- }
90
+ }
package/dist/cli.cjs DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env node
2
- var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=e=>{let n={};for(var r in e)t(n,r,{get:e[r],enumerable:!0});return n},s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let l=require(`node:util`);l=c(l);let u=require(`node:fs`);u=c(u);let d=require(`mp4box`);d=c(d);let f=require(`node:stream`);f=c(f);const p=async({key:e,iv:t,data:n,algorithm:r=`AES-CTR`})=>{let i=r.toUpperCase(),a=await crypto.subtle.importKey(`raw`,e,{name:i},!1,[`decrypt`]),o=await crypto.subtle.decrypt({name:i,counter:t,length:64},a,n);return new Uint8Array(o)},m=e=>{let t=e.buffer;return new DataView(t,e.byteOffset,e.byteLength)},ee=(e,t=0,n)=>new Uint8Array(e.buffer,e.byteOffset+t,n);var h=class extends Error{constructor(e,t,n,r){super(r),this.severity=e,this.category=t,this.code=n,this.name=`DataViewReaderError`}},g=class{dataView;littleEndian;position=0;constructor(e,t=`BE`){this.dataView=m(e),this.littleEndian=t===`LE`}getDataView(){return this.dataView}hasMoreData(){return this.position<this.dataView.byteLength}getPosition(){return this.position}getLength(){return this.dataView.byteLength}readUint8(){this.checkBounds(1);let e=this.dataView.getUint8(this.position);return this.position+=1,e}readUint16(){this.checkBounds(2);let e=this.dataView.getUint16(this.position,this.littleEndian);return this.position+=2,e}readUint32(){this.checkBounds(4);let e=this.dataView.getUint32(this.position,this.littleEndian);return this.position+=4,e}readInt32(){this.checkBounds(4);let e=this.dataView.getInt32(this.position,this.littleEndian);return this.position+=4,e}readUint64(){let e,t;try{this.littleEndian?(e=this.dataView.getUint32(this.position,!0),t=this.dataView.getUint32(this.position+4,!0)):(t=this.dataView.getUint32(this.position,!1),e=this.dataView.getUint32(this.position+4,!1))}catch{throw this.outOfBounds()}if(t>2097151)throw new h(`CRITICAL`,`MEDIA`,`JS_INTEGER_OVERFLOW`,`64-bit integer overflow`);return this.position+=8,t*2**32+e}readBytes(e){this.checkBounds(e);let t=ee(this.dataView,this.position,e);return this.position+=e,t}skip(e){this.checkBounds(e),this.position+=e}rewind(e){if(this.position<e)throw this.outOfBounds();this.position-=e}seek(e){if(e<0||e>this.dataView.byteLength)throw this.outOfBounds();this.position=e}readTerminatedString(){let e=this.position;for(;this.hasMoreData()&&this.dataView.getUint8(this.position)!==0;)this.position++;if(!this.hasMoreData())throw this.outOfBounds();let t=new Uint8Array(this.dataView.buffer,e,this.position-e);return this.position++,new TextDecoder().decode(t)}checkBounds(e){if(this.position+e>this.dataView.byteLength)throw this.outOfBounds()}outOfBounds(){return new h(`CRITICAL`,`MEDIA`,`BUFFER_READ_OUT_OF_BOUNDS`,`Read operation out of bounds`)}};const _=(e,t)=>{if(e.length===0)return new Uint8Array;let n=t??e.reduce((e,t)=>e+t.length,0);n=Math.min(n,e.reduce((e,t)=>e+t.length,0));let r=new Uint8Array(n),i=0;for(let t of e){if(i>=n)break;let e=t.subarray(0,Math.min(t.length,n-i));r.set(e,i),i+=e.length}return r},v=e=>e.match(/.{1,2}/g)?.map(e=>parseInt(e,16))??[];function y(e,t){let n=e.getBox(`tenc`);n.default_isProtected,n.default_KID;let r=n.default_Per_Sample_IV_Size,i=e.moofs[t],a=e.mdats[t],o=i.trafs[0],s=o.truns[0],c=o.senc,l=new g(c.data),u=l.readUint32();c.samples=[];for(let e=0;e<u;e++){s.sample_size[e],s.sample_duration[e];let t={},n=new Uint8Array(16);if(n.set(l.readBytes(r)),t.InitializationVector=n,c.flags&2){t.subsamples=[];let e=l.readUint16();for(let n=0;n<e;n++){let e={};e.BytesOfClearData=l.readUint16(),e.BytesOfProtectedData=l.readUint32(),t.subsamples.push(e)}}c.samples.push(t)}return{moof:i,mdat:a,traf:o,trun:s,senc:c}}var te=class{input;inputReady;output;buffer=new Uint8Array;bytesRead=0;processedBytes=0;samplesProcessingQueue=[];constructor(e={}){this.options=e,this.input=(0,d.createFile)(!0),this.output=(0,d.createFile)(!1),this.inputReady=new Promise(e=>{this.input.onReady=e}),this.inputReady.then(e=>{let t=e.tracks[0],n=t.nb_samples;this.input.setExtractionOptions(t.id,void 0,{nbSamples:n});let r=this.processInit();this.buffer=_([this.buffer,r]),this.input.onSamples=(e,t,n)=>{this.samplesProcessingQueue.push(this.processSamples(n))},this.input.start()})}processInit(){let e=new d.DataStream,t=this.output.ftyp,n=this.output.moov,r=n.traks[0].mdia.minf.stbl.stsd,i=r.entries?.find(e=>!e.box_name),a=i.sinf.frma,o=new d.BoxParser.sampleEntry[a.data_format];for(let e of Object.keys(i))o.set(e,i[e]);return o.boxes=o.boxes?.filter(e=>e.box_name!==`ProtectionSchemeInfoBox`),r.addEntry(o),r.entries=r.entries?.filter(e=>!!e.box_name),n.psshs=[],n.boxes=this.output.moov.boxes?.filter(e=>e.box_name!==`ProtectionSystemSpecificHeaderBox`),t.write(e),n.write(e),new Uint8Array(e.buffer)}async processSamples(e){let t=e[0].track_id,n=this.input.moofs[e[0].moof_number-1],r=n.trafs[0];r.truns[0];for(let t of e){let{senc:e}=y(this.input,t.moof_number-1),n=e.samples[t.number_in_traf],r=[],i=0;for(let e of n.subsamples)r.push(t.data.subarray(i+e.BytesOfClearData,i+e.BytesOfClearData+e.BytesOfProtectedData)),i+=e.BytesOfClearData+e.BytesOfProtectedData;let a=_(r),o=await this.options.transformSample?.({data:a,iv:n.InitializationVector,timestamp:t.cts,encryptionScheme:`cenc`}),s=[],c=0,l=0;for(let e of n.subsamples)s.push(t.data.subarray(c,c+e.BytesOfClearData)),c+=e.BytesOfClearData+e.BytesOfProtectedData,s.push(o.subarray(l,l+e.BytesOfProtectedData)),l+=e.BytesOfProtectedData;let u=_(s);t.data=u,t.size=u.byteLength}r.boxes=r.boxes?.filter(e=>e.box_name!==`PiffSampleEncryptionBox`&&e.box_name!==`SampleEncryptionBox`&&e.box_name!==`SampleAuxiliaryInformationOffsetsBox`&&e.box_name!==`SampleAuxiliaryInformationSizesBox`);let i=new d.DataStream;n.write(i),n.trafs[0].truns[0].data_offset=n.size+8,i.adjustUint32(n.trafs[0].truns[0].data_offset_position,n.trafs[0].truns[0].data_offset);let a=new d.BoxParser.box.mdat,o=_(e.map(e=>e.data)),s=d.MP4BoxBuffer.fromArrayBuffer(o.buffer,0);a.stream=new d.MultiBufferStream(s),a.write(i);let c=new Uint8Array(i.buffer),l=Math.max(...e.map(e=>e.moof_number))+1;return this.input.releaseUsedSamples(t,l),c}async transform(e,t){try{let n=d.MP4BoxBuffer.fromArrayBuffer(e.buffer,this.bytesRead);if(this.input.appendBuffer(n),this.output.appendBuffer(n),this.bytesRead+=e.length,this.samplesProcessingQueue.length>0){let e=await Promise.all(this.samplesProcessingQueue);this.samplesProcessingQueue=[];let t=_(e);this.buffer=_([this.buffer,t])}this.buffer.length>0&&(t.enqueue(this.buffer),this.updateProgress(this.buffer.byteLength),this.buffer=new Uint8Array)}catch(e){t.error(e)}}async flush(e){this.buffer.length>0&&(e.enqueue(this.buffer),this.updateProgress(this.buffer.byteLength))}updateProgress(e){this.processedBytes+=e,this.options.onProgress&&this.options.onProgress(this.processedBytes)}},ne=class extends TransformStream{constructor(e={}){let t=new te(e);super({transform:(e,n)=>t.transform(e,n),flush:e=>t.flush(e)})}},b=o({fs:()=>u,stream:()=>f.default});const x=b===void 0?void 0:b;var S=class{readable;constructor(e){this.filePath=e;let t=x.fs.createReadStream(e);this.readable=x.stream.Readable.toWeb(t)}},C=class{writable;constructor(e){this.filePath=e;let t=x.fs.createWriteStream(e);this.writable=x.stream.Writable.toWeb(t)}},w=class{source;constructor(e){this.source=e.source}},T=class{target;constructor(e){this.target=e.target}},E=class e{decrypt;input;output;static async init(t){let n=new e(t);return await n.#init(),n}constructor(e){this.input=e.input,this.output=e.output;let t=new Uint8Array(v(e.keys[0].key));this.decrypt=new ne({transformSample:async e=>D(t,e),onProgress:e.onProgress})}async#init(){}async execute(){let e=this.input.source.readable,t=this.output.target.writable,n=this.decrypt;await e.pipeThrough(n).pipeTo(t)}};const D=async(e,t)=>{let n=t.encryptionScheme,r=t.iv,i=t.data;return p(n===`cbcs`?{key:e,iv:r,data:i,algorithm:`AES-CBC`}:{key:e,iv:r,data:i,algorithm:`AES-CTR`})},O=async(e,t,n)=>{console.time(`
3
- Done!`);let r=new w({source:new S(e)}),i=new T({target:new C(t)});await(await E.init({input:r,output:i,keys:[{kid:n.keyId,key:`key`in n?n.key:``}],onProgress:e=>process.stdout.write(`\rDecrypting... [${e}]`)})).execute(),console.timeEnd(`
4
- Done!`)};var k=`shifro`,A=`0.2.0`,j=`A lightweight library for decrypting MP4 files, directly in the browser`,M=[`dist`],N={start:`node ./dist/cli.cjs`,web:`vite`,build:`npm run build:lib && npm run build:cli`,"build:cli":`tsdown ./lib/node/cli.ts --format cjs --dts --minify --no-clean`,"build:lib":`tsdown shifro.ts --format cjs --format esm --dts --platform neutral`,typecheck:`tsc --noEmit`,test:`vitest`,prepublishOnly:`npm run build`},P={shifro:`./dist/cli.cjs`},F=`module`,I=`./dist/shifro.js`,re=`./dist/shifro.d.ts`,L={".":{require:{types:`./dist/shifro.d.cts`,default:`./dist/shifro.cjs`},import:{types:`./dist/shifro.d.ts`,default:`./dist/shifro.js`}}},R={type:`git`,url:`git+https://github.com/streamyx-labs/shifro.git`},z=[`mp4`,`mpeg`,`cenc`,`cbcs`,`decrypt`,`decryption`,`decrypter`,`decryptor`,`dash`,`drm`,`mp4decrypt`,`mp4box`,`shaka`,`shaka-packager`],B={url:`https://github.com/streamyx-labs/shifro/issues`,email:`vitalygashkov@vk.com`},V=`Vitaly Gashkov <vitalygashkov@vk.com>`,H=`AGPL-3.0`,U=`README.md`,W=[{type:`individual`,url:`https://t.me/tribute/app?startapp=dqW2`}],G={node:`>=20`},K={"@types/node":`^24.5.2`,tsdown:`^0.15.5`,typescript:`^5.9.2`,vitest:`^3.2.4`},q={mp4box:`^2.1.1`},J={name:k,version:A,description:j,files:M,scripts:N,bin:P,type:F,main:I,types:re,exports:L,repository:R,keywords:z,bugs:B,author:V,license:H,readmeFilename:U,funding:W,engines:G,devDependencies:K,dependencies:q};const Y=(0,l.parseArgs)({allowPositionals:!0,strict:!1,options:{key:{short:`k`,type:`string`,multiple:!0},help:{short:`h`,type:`boolean`}}}),[X,Z]=(Array.isArray(Y.values.key)?Y.values.key:[])[0]?.split(`:`)??[],[Q,$]=Y.positionals;(async()=>{Q&&$&&X&&Z?await O(Q,$,{key:Z,keyId:X}):console.log(`
5
- ${J.name} version ${J.version}
6
- (c) 2024-2025 ${J.author}
7
-
8
- Usage: ${J.name} [options] <input> <output>
9
-
10
- Options:
11
- --key <id>:<k>
12
- <id> is either a track ID in decimal or a 128-bit KID in hex,
13
- <k> is a 128-bit key in hex
14
- (several --key options can be used, one for each track or KID)
15
- `.trim())})();