shifro 0.1.2 → 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/README.md CHANGED
@@ -1,154 +1,85 @@
1
1
  # shifro
2
2
 
3
- A lightweight, dependency-free MP4 decrypter
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 subsample 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 Node.js streams
36
-
37
- ```js
38
- import { createWriteStream, createReadStream } from 'node:fs';
39
- import { stat } from 'node:fs/promises';
40
- import { Readable, Writable } from 'node:stream';
41
- import { decryptStream } from 'shifro';
42
-
43
- const key = 'eb676abbcb345e96bbcf616630f1a3da';
44
- const keyId = '100b6c20940f779a4589152b57d2dacb';
45
-
46
- const inputPath = './input.mp4';
47
- const inputInfo = await stat(inputPath);
48
- const inputNodeStream = createReadStream(inputPath, { highWaterMark: 1024 * 1024 * 10 });
49
- const inputWebStream = Readable.toWeb(inputNodeStream);
50
-
51
- const outputPath = './output.mp4';
52
- const outputNodeStream = createWriteStream(outputPath);
53
- const outputWebStream = Writable.toWeb(outputNodeStream);
54
-
55
- await decryptStream(inputWebStream, outputWebStream, {
56
- key,
57
- keyId,
58
- onProgress: (progress) => {
59
- process.stdout.write(`\rDecrypting... [${progress}/${inputInfo.size}]`);
60
- if (progress === inputInfo.size) process.stdout.write('\n');
61
- },
62
- });
63
- ```
64
-
65
- #### Decrypting file using browser's Web Streams API
66
-
67
- ```js
68
- import { decryptStream } from 'shifro';
21
+ Run `npm install shifro` to install the library as dependency for your project.
69
22
 
70
- const key = 'eb676abbcb345e96bbcf616630f1a3da';
71
- const keyId = '100b6c20940f779a4589152b57d2dacb';
23
+ #### Decrypting file using Node.js streams
72
24
 
73
- const waitForInput = async () =>
74
- new Promise<File>((resolve) => {
75
- const input = document.querySelector<HTMLInputElement>('#input')!;
76
- input.addEventListener('change', () => resolve(input.files![0]));
25
+ ```ts
26
+ import { Decryption, Input, FilePathSource, KeyId, Key, Output, FilePathTarget } from 'shifro';
27
+
28
+ async function decrypt() {
29
+ const decryption = await Decryption.init({
30
+ input: new Input({
31
+ source: new FilePathSource('./input.mp4'),
32
+ keys: new Map<KeyId, Key>([
33
+ ['4d97930a3d7b55fa81d0028653f5e499', '429ec76475e7a952d224d8ef867f12b6'],
34
+ ['d21373c0b8ab5ba9954742bcdfb5f48b', '150a6c7d7dee6a91b74dccfce5b31928'],
35
+ ])
36
+ }),
37
+ output: new Output({ target: new FilePathTarget('./output.mp4') }),
77
38
  });
78
39
 
79
- const inputFile = await waitForInput();
80
- const inputStream = inputFile.stream();
81
-
82
- const outputFileHandle = window.showSaveFilePicker({ suggestedName: 'output.mp4', startIn: 'downloads' });
83
- const outputStream = await outputFileHandle.createWritable();
40
+ decryption.onProgress = (progress) => process.stdout.write(`\rDecrypting... [${Math.round(progress * 100)}%]`),
84
41
 
85
- await decryptStream(inputStream, outputStream, {
86
- key,
87
- keyId,
88
- onProgress: (progress) => console.log(`${progress}/${input.size}`),
89
- });
42
+ await decryption.execute();
43
+ }
90
44
  ```
91
45
 
92
- #### Segment-by-segment decryption
93
-
94
- ```js
95
- import { decryptSegment } from 'shifro';
46
+ #### Decrypting file using browser's Web Streams API
96
47
 
97
- const key = 'eb676abbcb345e96bbcf616630f1a3da';
98
- const keyId = '100b6c20940f779a4589152b57d2dacb';
99
- const encryptionScheme = 'cenc';
48
+ ```ts
49
+ import { Decryption, Input, ReadableStreamSource, KeyId, Key, Output, StreamTarget } from 'shifro';
50
+
51
+ async function decryptFromBrowser() {
52
+ const handleFileSelect = async () => {
53
+ return new Promise<File>((resolve) => {
54
+ const input = document.querySelector<HTMLInputElement>('#input')!;
55
+ input.addEventListener('change', () => resolve(input.files![0]));
56
+ });
57
+ }
58
+
59
+ const inputFile = await handleFileSelect();
60
+ const inputReadableStream = inputFile.stream();
61
+
62
+ const outputFileHandle = window.showSaveFilePicker({ suggestedName: 'output.mp4', startIn: 'downloads' });
63
+ const outputWritableStream = await outputFileHandle.createWritable();
64
+
65
+ const decryption = await Decryption.init({
66
+ input: new Input({
67
+ source: new ReadableStreamSource(inputReadableStream),
68
+ keys: new Map<KeyId, Key>([
69
+ ['4d97930a3d7b55fa81d0028653f5e499', '429ec76475e7a952d224d8ef867f12b6'],
70
+ ['d21373c0b8ab5ba9954742bcdfb5f48b', '150a6c7d7dee6a91b74dccfce5b31928'],
71
+ ])
72
+ }),
73
+ output: new Output({ target: new StreamTarget(outputWritableStream) }),
74
+ });
100
75
 
101
- const encryptedSegments = [
102
- // List of binary MP4 segments (Uint8Array)
103
- // ...
104
- ];
105
- const decryptedSegments = [];
76
+ decryption.onProgress = (progress) => console.log(`Decrypting... [${Math.round(progress * 100)}%]`),
106
77
 
107
- for (const segment of encryptedSegments) {
108
- const decrypted = await decryptSegment(segment, { key, keyId, encryptionScheme });
109
- decryptedSegments.push(decrypted);
78
+ await decryption.execute();
110
79
  }
111
-
112
- // Do something with results...
113
80
  ```
114
81
 
115
- #### Transform sample data in your own way instead of built-in decryption
116
-
117
- ```js
118
- import { createDecipheriv } from 'node:crypto';
119
- import { decryptSegment } from 'shifro';
120
-
121
- const key = 'eb676abbcb345e96bbcf616630f1a3da';
122
- const keyId = '100b6c20940f779a4589152b57d2dacb';
123
- const encryptionScheme = 'cenc';
124
-
125
- const encryptedSegments = [
126
- // List of binary MP4 segments (Uint8Array)
127
- // ...
128
- ];
129
- const decryptedSegments = [];
130
-
131
- for (const segment of encryptedSegments) {
132
- const decrypted = await decryptSegment(segment, {
133
- keyId,
134
- transformSample: (params) => {
135
- // Implement custom handler for subsample processing
136
- // ...
137
- const sampleData = params.data;
138
- const iv = params.iv;
139
- const decipher = createDecipheriv('aes-128-ctr', key, iv);
140
- const decrypted = Buffer.concat([decipher.update(sampleData), decipher.final()]);
141
- return decrypted;
142
- },
143
- });
144
- decryptedSegments.push(decrypted);
145
- }
146
-
147
- // Do something with results...
148
- ```
149
82
 
150
- ### CLI
83
+ ## Credits
151
84
 
152
- ```bash
153
- shifro --key eb676abbcb345e96bbcf616630f1a3da:100b6c20940f779a4589152b57d2dacb ./input.mp4 ./output.mp4
154
- ```
85
+ - [mediabunny](https://github.com/Vanilagy/mediabunny)
package/dist/cli.mjs ADDED
@@ -0,0 +1,81 @@
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.0.0";
7
+ var author = "Vitaly Gashkov <vitalygashkov@vk.com>";
8
+ //#endregion
9
+ //#region src/main.ts
10
+ var Input$1 = class extends Input {
11
+ constructor({ keys, ...options }) {
12
+ super({
13
+ ...options,
14
+ formats: ALL_FORMATS,
15
+ formatOptions: { isobmff: { resolveKeyId: ({ keyId }) => {
16
+ const key = keys.get(keyId);
17
+ if (!key) throw new Error("Unknown key ID.");
18
+ return key;
19
+ } } }
20
+ });
21
+ }
22
+ };
23
+ var Output$1 = class extends Output {
24
+ constructor({ ...options }) {
25
+ super({
26
+ ...options,
27
+ format: options.format ?? new Mp4OutputFormat()
28
+ });
29
+ }
30
+ };
31
+ //#endregion
32
+ //#region src/cli.ts
33
+ const args = parseArgs({
34
+ allowPositionals: true,
35
+ strict: false,
36
+ options: {
37
+ key: {
38
+ short: "k",
39
+ type: "string",
40
+ multiple: true
41
+ },
42
+ help: {
43
+ short: "h",
44
+ type: "boolean"
45
+ }
46
+ }
47
+ });
48
+ const keyStrings = Array.isArray(args.values.key) ? args.values.key : [];
49
+ const [inputPath, outputPath] = args.positionals;
50
+ const decryptFile = async () => {
51
+ console.time("\nDone!");
52
+ const decryption = await Conversion.init({
53
+ input: new Input$1({
54
+ source: new FilePathSource(inputPath),
55
+ keys: new Map(keyStrings.map((keyString) => keyString.split(":")))
56
+ }),
57
+ output: new Output$1({ target: new FilePathTarget(outputPath) })
58
+ });
59
+ decryption.onProgress = (progress) => process.stdout.write(`\rDecrypting... [${Math.round(progress * 100)}%]`), await decryption.execute();
60
+ console.timeEnd("\nDone!");
61
+ };
62
+ const showHelp = () => {
63
+ console.log(`
64
+ ${name} version ${version}
65
+ (c) 2024-2026 ${author}
66
+
67
+ Usage: ${name} [options] <input> <output>
68
+
69
+ Options:
70
+ --key <id>:<k>
71
+ <id> is either a track ID in decimal or a 128-bit KID in hex,
72
+ <k> is a 128-bit key in hex
73
+ (several --key options can be used, one for each track or KID)
74
+ `.trim());
75
+ };
76
+ (async () => {
77
+ if (inputPath && outputPath && keyStrings.length > 0) await decryptFile();
78
+ else showHelp();
79
+ })();
80
+ //#endregion
81
+ export {};
package/dist/index.cjs ADDED
@@ -0,0 +1,43 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let mediabunny = require("mediabunny");
3
+ //#region src/main.ts
4
+ var Input = class extends mediabunny.Input {
5
+ constructor({ keys, ...options }) {
6
+ super({
7
+ ...options,
8
+ formats: mediabunny.ALL_FORMATS,
9
+ formatOptions: { isobmff: { resolveKeyId: ({ keyId }) => {
10
+ const key = keys.get(keyId);
11
+ if (!key) throw new Error("Unknown key ID.");
12
+ return key;
13
+ } } }
14
+ });
15
+ }
16
+ };
17
+ var Output = class extends mediabunny.Output {
18
+ constructor({ ...options }) {
19
+ super({
20
+ ...options,
21
+ format: options.format ?? new mediabunny.Mp4OutputFormat()
22
+ });
23
+ }
24
+ };
25
+ //#endregion
26
+ exports.Decryption = mediabunny.Conversion;
27
+ exports.FilePathSource = mediabunny.FilePathSource;
28
+ exports.FilePathTarget = mediabunny.FilePathTarget;
29
+ exports.Input = Input;
30
+ exports.InputOptions = mediabunny.InputOptions;
31
+ exports.MkvOutputFormat = mediabunny.MkvOutputFormat;
32
+ exports.MovOutputFormat = mediabunny.MovOutputFormat;
33
+ exports.Mp3OutputFormat = mediabunny.Mp3OutputFormat;
34
+ exports.Mp4OutputFormat = mediabunny.Mp4OutputFormat;
35
+ exports.Output = Output;
36
+ exports.OutputFormat = mediabunny.OutputFormat;
37
+ exports.OutputOptions = mediabunny.OutputOptions;
38
+ exports.ReadableStreamSource = mediabunny.ReadableStreamSource;
39
+ exports.StreamSource = mediabunny.StreamSource;
40
+ exports.StreamSourceOptions = mediabunny.StreamSourceOptions;
41
+ exports.StreamTarget = mediabunny.StreamTarget;
42
+ exports.StreamTargetOptions = mediabunny.StreamTargetOptions;
43
+ exports.UrlSource = mediabunny.UrlSource;
@@ -0,0 +1,23 @@
1
+ import { Conversion, FilePathSource, FilePathTarget, Input as Input$1, InputOptions, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output as Output$1, OutputFormat, OutputOptions, ReadableStreamSource, StreamSource, StreamSourceOptions, StreamTarget, StreamTargetOptions, UrlSource } from "mediabunny";
2
+
3
+ //#region src/main.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
+ ...options
11
+ }: Omit<InputOptions, 'formats' | 'formatOptions'> & {
12
+ keys: KeyMap;
13
+ });
14
+ }
15
+ declare class Output extends Output$1 {
16
+ constructor({
17
+ ...options
18
+ }: Omit<OutputOptions, 'format'> & {
19
+ format?: OutputFormat;
20
+ });
21
+ }
22
+ //#endregion
23
+ export { Conversion as Decryption, FilePathSource, FilePathTarget, Input, InputOptions, Key, KeyId, KeyMap, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output, OutputFormat, OutputOptions, ReadableStreamSource, StreamSource, StreamSourceOptions, StreamTarget, StreamTargetOptions, UrlSource };
@@ -0,0 +1,23 @@
1
+ import { Conversion, FilePathSource, FilePathTarget, Input as Input$1, InputOptions, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output as Output$1, OutputFormat, OutputOptions, ReadableStreamSource, StreamSource, StreamSourceOptions, StreamTarget, StreamTargetOptions, UrlSource } from "mediabunny";
2
+
3
+ //#region src/main.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
+ ...options
11
+ }: Omit<InputOptions, 'formats' | 'formatOptions'> & {
12
+ keys: KeyMap;
13
+ });
14
+ }
15
+ declare class Output extends Output$1 {
16
+ constructor({
17
+ ...options
18
+ }: Omit<OutputOptions, 'format'> & {
19
+ format?: OutputFormat;
20
+ });
21
+ }
22
+ //#endregion
23
+ export { Conversion as Decryption, FilePathSource, FilePathTarget, Input, InputOptions, Key, KeyId, KeyMap, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output, OutputFormat, OutputOptions, ReadableStreamSource, StreamSource, StreamSourceOptions, StreamTarget, StreamTargetOptions, UrlSource };
package/dist/index.js ADDED
@@ -0,0 +1,25 @@
1
+ import { ALL_FORMATS, Conversion, FilePathSource, FilePathTarget, Input as Input$1, InputOptions, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output as Output$1, OutputFormat, OutputOptions, ReadableStreamSource, StreamSource, StreamSourceOptions, StreamTarget, StreamTargetOptions, UrlSource } from "mediabunny";
2
+ //#region src/main.ts
3
+ var Input = class extends Input$1 {
4
+ constructor({ keys, ...options }) {
5
+ super({
6
+ ...options,
7
+ formats: ALL_FORMATS,
8
+ formatOptions: { isobmff: { resolveKeyId: ({ keyId }) => {
9
+ const key = keys.get(keyId);
10
+ if (!key) throw new Error("Unknown key ID.");
11
+ return key;
12
+ } } }
13
+ });
14
+ }
15
+ };
16
+ var Output = class extends Output$1 {
17
+ constructor({ ...options }) {
18
+ super({
19
+ ...options,
20
+ format: options.format ?? new Mp4OutputFormat()
21
+ });
22
+ }
23
+ };
24
+ //#endregion
25
+ export { Conversion as Decryption, FilePathSource, FilePathTarget, Input, InputOptions, MkvOutputFormat, MovOutputFormat, Mp3OutputFormat, Mp4OutputFormat, Output, OutputFormat, OutputOptions, ReadableStreamSource, StreamSource, StreamSourceOptions, StreamTarget, StreamTargetOptions, UrlSource };
package/package.json CHANGED
@@ -1,41 +1,45 @@
1
1
  {
2
2
  "name": "shifro",
3
- "version": "0.1.2",
4
- "description": "A lightweight, dependency-free MP4 decrypter",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight library for decrypting MP4 files, directly in the browser",
5
5
  "files": [
6
6
  "dist"
7
7
  ],
8
8
  "scripts": {
9
- "start": "node ./dist/cli.cjs",
9
+ "start": "node ./dist/cli.mjs",
10
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 --minify",
11
+ "build": "npm run bundle:lib && npm run bundle:cli",
12
+ "bundle:lib": "tsdown ./src/index.ts --format cjs --format esm --dts --platform neutral",
13
+ "bundle:cli": "tsdown ./src/cli.ts --format esm --no-clean",
14
+ "build:cli": "npm run build:cli:mac && npm run build:cli:linux && npm run build:cli:win",
15
+ "build:cli:mac": "bun build --compile --minify --bytecode --target=bun-darwin-arm64 ./src/cli.ts --outfile shifro-windows.exe",
16
+ "build:cli:linux": "bun build --compile --minify --bytecode --target=bun-linux-x64 ./src/cli.ts --outfile shifro-linux",
17
+ "build:cli:win": "bun build --compile --minify --bytecode --target=bun-windows-x64 ./src/cli.ts --outfile shifro-macos",
14
18
  "typecheck": "tsc --noEmit",
15
19
  "test": "vitest",
16
20
  "prepublishOnly": "npm run build"
17
21
  },
18
22
  "bin": {
19
- "shifro": "./dist/cli.cjs"
23
+ "shifro": "./dist/cli.mjs"
20
24
  },
21
25
  "type": "module",
22
- "main": "./dist/shifro.js",
23
- "types": "./dist/shifro.d.ts",
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
24
28
  "exports": {
25
29
  ".": {
26
30
  "require": {
27
- "types": "./dist/shifro.d.cts",
28
- "default": "./dist/shifro.cjs"
31
+ "types": "./dist/index.d.cts",
32
+ "default": "./dist/index.cjs"
29
33
  },
30
34
  "import": {
31
- "types": "./dist/shifro.d.ts",
32
- "default": "./dist/shifro.js"
35
+ "types": "./dist/index.d.ts",
36
+ "default": "./dist/index.js"
33
37
  }
34
38
  }
35
39
  },
36
40
  "repository": {
37
41
  "type": "git",
38
- "url": "git+https://github.com/streamyx-labs/shifro.git"
42
+ "url": "git+https://github.com/azot-labs/shifro.git"
39
43
  },
40
44
  "keywords": [
41
45
  "mp4",
@@ -54,29 +58,28 @@
54
58
  "shaka-packager"
55
59
  ],
56
60
  "bugs": {
57
- "url": "https://github.com/streamyx-labs/shifro/issues",
61
+ "url": "https://github.com/azot-labs/shifro/issues",
58
62
  "email": "vitalygashkov@vk.com"
59
63
  },
60
64
  "author": "Vitaly Gashkov <vitalygashkov@vk.com>",
61
- "license": "AGPL-3.0",
65
+ "license": "MIT",
62
66
  "readmeFilename": "README.md",
63
67
  "funding": [
64
68
  {
65
69
  "type": "individual",
66
- "url": "https://boosty.to/vitalygashkov"
67
- },
68
- {
69
- "type": "patreon",
70
- "url": "https://www.patreon.com/vitalygashkov"
70
+ "url": "https://t.me/tribute/app?startapp=dqW2"
71
71
  }
72
72
  ],
73
73
  "engines": {
74
- "node": "20 || 21 || 22 || 23"
74
+ "node": ">=22"
75
+ },
76
+ "dependencies": {
77
+ "mediabunny": "^1.42.0-beta.5"
75
78
  },
76
79
  "devDependencies": {
77
- "@types/node": "^22.15.3",
78
- "tsdown": "^0.11.0-beta.3",
79
- "typescript": "^5.8.3",
80
- "vitest": "^3.1.2"
80
+ "@types/node": "^24.5.2",
81
+ "tsdown": "^0.21.10",
82
+ "typescript": "^5.9.2",
83
+ "vitest": "^4.1.5"
81
84
  }
82
85
  }
package/dist/cli.cjs DELETED
@@ -1,16 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(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},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));const c=s(require(`node:util`)),l=s(require(`node:fs`)),u=s(require(`node:fs/promises`)),d=s(require(`node:stream`)),f=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)},p=e=>{let t=e.buffer,n=new DataView(t,e.byteOffset,e.byteLength);return n},m=(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{position=0;constructor(e,t=`BE`){this.dataView=p(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=m(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`)}};let _=function(e){return e[e.BASIC_BOX=0]=`BASIC_BOX`,e[e.FULL_BOX=1]=`FULL_BOX`,e}({});var v=class e{headers=new Map;boxDefinitions=new Map;done=!1;box(t,n){let r=e.typeFromString(t);return this.headers.set(r,_.BASIC_BOX),this.boxDefinitions.set(r,n),this}fullBox(t,n){let r=e.typeFromString(t);return this.headers.set(r,_.FULL_BOX),this.boxDefinitions.set(r,n),this}stop(){this.done=!0}parse(e,t=!1,n=!1){let r=new g(e);for(this.done=!1;r.hasMoreData()&&!this.done;)this.parseNext(0,r,t,n)}parseNext(t,n,r,i){let a=n.getPosition();if(i&&a+8>n.getLength()){this.done=!0;return}let o=n.readUint32(),s=n.readUint32(),c=e.typeToString(s),l=!1;switch(o){case 0:o=n.getLength()-a;break;case 1:if(i&&n.getPosition()+8>n.getLength()){this.done=!0;return}o=Number(n.readUint64()),l=!0;break}let u=this.boxDefinitions.get(s);if(u){let e=null,d=null;if(this.headers.get(s)===_.FULL_BOX){if(i&&n.getPosition()+4>n.getLength()){this.done=!0;return}let t=n.readUint32();e=t>>>24,d=t&16777215}let f=a+o;if(r&&f>n.getLength()&&(f=n.getLength()),i&&f>n.getLength()){this.done=!0;return}let p=f-n.getPosition(),m=p>0?n.readBytes(p):new Uint8Array,h=new g(m),v={name:c,parser:this,partialOkay:!!r,version:e,flags:d,reader:h,size:o,start:a+t,has64BitSize:l};u(v)}else{let e=Math.min(a+o-n.getPosition(),n.getLength()-n.getPosition());n.skip(e)}}static children(t){let n=e.headerSize(t);for(;t.reader.hasMoreData()&&!t.parser.done;)t.parser.parseNext(t.start+n,t.reader,t.partialOkay)}static sampleDescription(t){let n=e.headerSize(t),r=t.reader.readUint32();for(let e=0;e<r&&(t.parser.parseNext(t.start+n,t.reader,t.partialOkay),!t.parser.done);e++);}static visualSampleEntry(t){let n=e.headerSize(t);for(t.reader.skip(78);t.reader.hasMoreData()&&!t.parser.done;)t.parser.parseNext(t.start+n,t.reader,t.partialOkay)}static audioSampleEntry(t){let n=e.headerSize(t);t.reader.skip(8);let r=t.reader.readUint16();for(t.reader.skip(6),r===2?t.reader.skip(48):t.reader.skip(12),r===1&&t.reader.skip(16);t.reader.hasMoreData()&&!t.parser.done;)t.parser.parseNext(t.start+n,t.reader,t.partialOkay)}static allData(e){return t=>{let n=t.reader.getLength()-t.reader.getPosition();e(t.reader.readBytes(n))}}static typeFromString(e){if(e.length!==4)throw Error(`MP4 box names must be 4 characters long`);let t=0;for(let n of e)t=t<<8|n.charCodeAt(0);return t}static typeToString(e){return String.fromCharCode(e>>24&255,e>>16&255,e>>8&255,e&255)}static headerSize(e){let t=8,n=e.has64BitSize?8:0,r=e.flags===null?0:4;return t+n+r}};const ee=(e,t)=>{if(t&&t&1){let t=e.readUint8(),n=e.readUint8(),r=e.readBytes(16)}let n=e.readUint32(),r=[];for(let i=0;i<n;i++){let n=8,i=new Uint8Array(16);i.set(e.readBytes(n));let a={iv:i,subsamples:[]},o=t&&t&2;if(o){let t=e.readUint16();for(let n=0;n<t;n++){let t=e.readUint16(),n=e.readUint32();a.subsamples.push({bytesOfClearData:t,bytesOfEncryptedData:n})}}r.push(a)}return{samples:r}},y=(e,t,n)=>{let r=e.readUint32(),i=[],a=null;t&1&&(a=e.readInt32()),t&4&&e.skip(4);for(let a=0;a<r;a++){let r={};t&256&&(r.duration=e.readUint32()),t&512&&(r.size=e.readUint32()),t&1024&&e.skip(4),t&2048&&(r.compositionTimeOffset=n==0?e.readUint32():e.readInt32()),i.push(r)}return{samples:i,dataOffset:a}},te=(e,t)=>{let n,r,i,a,o=e.readUint32();return t&1&&(i=e.readUint64()),t&2&&(a=e.readUint32()),t&8&&(n=e.readUint32()),t&16&&(r=e.readUint32()),{trackId:o,defaultSampleDuration:n,defaultSampleSize:r,baseDataOffset:i,sampleDescriptionIndex:a}},b=(e,t,n)=>{if(t.length===0)return-1;let r=e.length-t.length+1;for(let i=n;i<r;i++){let n=!0;for(let r=0;r<t.length;r++)if(e[i+r]!==t[r]){n=!1;break}if(n)return i}return-1},x=(e,t,n)=>{let r=new TextEncoder,i=r.encode(t),a=r.encode(n);if(i.length!==a.length)throw Error(`Original and replacement must have the same byte length`);let o=0;do o=b(e,i,0),o!==-1&&e.set(a,o);while(o!==-1)},S=(e,t,n=0)=>{let r=new TextEncoder,i;return i=typeof t==`string`?r.encode(t):typeof t==`number`?new Uint8Array([t]):t,i.length===0?!0:e.length<i.length?!1:b(e,i,n)!==-1},C=(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},ne=(e,t,n=0,r=0,i=e.length)=>{n=Math.max(0,Math.min(n,t.length)),r=Math.max(0,Math.min(r,e.length)),i=Math.min(i,e.length),i<r&&(i=r);let a=Math.min(i-r,t.length-n);return a<=0?0:(t.set(e.subarray(r,r+a),n),a)},re=(e,t,n=0,r)=>{let i=new TextEncoder,a=i.encode(t),o=Math.max(0,Math.min(n,e.length)),s=e.length-o,c=a.length;return typeof r==`number`&&(c=Math.min(c,r)),c=Math.min(c,s),c<=0?0:(e.set(a.subarray(0,c),o),c)},w=e=>Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),ie=e=>{let t=Array.from(e,e=>String.fromCodePoint(e)).join(``);return btoa(t)},ae=e=>e.match(/.{1,2}/g)?.map(e=>parseInt(e,16))??[],T=e=>{e.readUint8(),e.readUint8(),e.readUint8(),e.readUint8();let t=w(e.readBytes(16));return{defaultKID:t}},E=e=>{let t=`edef8ba979d64acea3c827dcd51d21ed`,n=e.version??0;if(n!==0&&n!==1)throw Error(`PSSH version can only be 0 or 1`);let r=e.reader.readBytes(16),i=w(r),a=[i.slice(0,8),i.slice(8,12),i.slice(12,16),i.slice(16,20),i.slice(20)].join(`-`),o=e.reader.getDataView(),s=new Uint8Array(o.buffer,o.byteOffset-12,e.size),c=ie(s),l=[];if(n>=1){let t=e.reader.readUint32();for(let n=0;n<t;n++)l.push(w(e.reader.readBytes(16)))}let u=e.reader.readUint32(),d=e.reader.readBytes(u),f=i===t;return{version:n,systemId:a,pssh:c,keyIds:l,systemData:d}},D=e=>{try{let t=!1,n=!1;return new v().box(`moov`,()=>{t=!0}).box(`moof`,()=>{n=!0}).parse(e,!0,!0),t&&!n}catch(e){return console.log(e),!1}},O=e=>{let t=null;return new v().box(`moov`,v.children).box(`trak`,v.children).box(`mdia`,v.children).box(`minf`,v.children).box(`stbl`,v.children).fullBox(`stsd`,v.sampleDescription).box(`encv`,v.visualSampleEntry).box(`enca`,v.audioSampleEntry).box(`sinf`,v.children).box(`frma`,e=>{let n=e.reader.readBytes(4);t=new TextDecoder().decode(n)}).parse(e,!0,!0),t},k=async e=>{let t=O(e);if(x(e,`sinf`,`skip`),x(e,`pssh`,`skip`),t)x(e,`encv`,t),x(e,`enca`,t);else{console.warn(`Could not detect original codec, using fallback values`);let t=S(e,`encv`)?`video`:`audio`;t===`video`?x(e,`encv`,`avc1`):x(e,`enca`,`mp4a`)}return e},A=e=>{let t={schemeType:``,defaultKID:``,psshList:[]};return new v().box(`moov`,v.children).box(`trak`,v.children).box(`mdia`,v.children).box(`minf`,v.children).box(`stbl`,v.children).fullBox(`stsd`,v.sampleDescription).fullBox(`pssh`,e=>t.psshList.push(E(e))).box(`encv`,v.visualSampleEntry).box(`enca`,v.audioSampleEntry).box(`sinf`,v.children).box(`schi`,v.children).fullBox(`schm`,e=>{t.schemeType=new TextDecoder().decode(e.reader.readBytes(4))}).fullBox(`tenc`,e=>{let{defaultKID:n}=T(e.reader);t.defaultKID=n}).parse(e,!1,!0),t},j=async(e,t)=>{let n=D(e);if(n)return k(e);let r,i,a,o;if(new v().box(`moof`,v.children).box(`traf`,v.children).fullBox(`tfhd`,e=>{a=te(e.reader,e.flags)}).fullBox(`trun`,e=>{i=y(e.reader,e.flags,e.version)}).fullBox(`senc`,e=>{r=ee(e.reader,e.flags)}).parse(e,!0,!0),o=i.dataOffset,r.samples.length!==i.samples.length)throw Error(`sample count mismatch: trun has ${i.samples.length}, senc has ${r.samples.length}`);let s=0,c=0;for(let n=0;n<r.samples.length;n++){let l=r.samples[n],u=i.samples[n],d=i.samples[n].size||a.defaultSampleSize||0;l.subsamples.length||l.subsamples.push({bytesOfClearData:0,bytesOfEncryptedData:d});let f=l.subsamples.some(e=>e.bytesOfEncryptedData>0);if(f){let n=0,r=[];for(let t of l.subsamples){if(n+=t.bytesOfClearData,t.bytesOfEncryptedData>0){let i=e.subarray(o+s+n,o+s+n+t.bytesOfEncryptedData);r.push(i)}n+=t.bytesOfEncryptedData}let i=C(r),a={iv:l.iv,data:i,timestamp:c},u=await t(a);if(u){n=0;let t=0,r=[];for(let i of l.subsamples){if(i.bytesOfClearData>0){let t=e.subarray(o+s+n,o+s+n+i.bytesOfClearData);r.push(t),n+=i.bytesOfClearData}if(i.bytesOfEncryptedData>0){let e=u.subarray(t,t+i.bytesOfEncryptedData);r.push(e),t+=i.bytesOfEncryptedData,n+=i.bytesOfEncryptedData}}let i=C(r);ne(i,e,o+s)}}s+=d,c+=u.duration||a.defaultSampleDuration||0}return new v().box(`moof`,v.children).box(`traf`,v.children).box(`senc`,t=>{let n=`skip`,r=t.start+n.length;re(e,n,r)}).parse(e,!0,!0),e},M=e=>{let t=null;new v().box(`moov`,e=>{t=e.start+e.size}).parse(e,!0,!0);let n=t?e.subarray(0,t):null;return{moovEnd:t,init:n}},N=e=>{let t=null,n=null;new v().box(`moof`,e=>{t===null&&(t=e.start)}).box(`mdat`,e=>{n===null&&(n=e.start+e.size)}).parse(e,!0,!0);let r=t!==null&&n!==null,i=r?e.subarray(t,n):null;return{segment:i,moofStart:t,mdatEnd:n}};var P=class{buffer=new Uint8Array;isProcessingInit=!0;scheme=null;processedBytes=0;constructor(e){this.options=e}async transform(e,t){try{this.buffer=C([this.buffer,e]);let n=!0;for(;n;){if(this.isProcessingInit){let{init:e,moovEnd:n}=M(this.buffer);if(!n||this.buffer.length<n)break;if(e&&D(e)){let r=A(e);this.scheme=r.schemeType;let i=await k(e);t.enqueue(i),this.updateProgress(i.byteLength),this.buffer=this.buffer.subarray(n),this.isProcessingInit=!1;continue}}let{moofStart:e,mdatEnd:r,segment:i}=N(this.buffer);if(e===null||r===null||!i||this.buffer.length<r)break;let a=e=>(e.encryptionScheme=this.scheme,this.options.transformSample(e)),o=await j(i,a);t.enqueue(o),this.updateProgress(o.byteLength),this.buffer=this.buffer.subarray(r),n=this.buffer.length>=8}}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)}};const F=async(e,t,{transformSample:n,onProgress:r,preventClose:i})=>{let a=new P({transformSample:n,onProgress:r}),o=new TransformStream({transform:(e,t)=>a.transform(e,t),flush:e=>a.flush(e)});await e.pipeThrough(o).pipeTo(t,{preventClose:i})},I=async(e,t)=>{let n=t.encryptionScheme,r=t.iv,i=t.data;return f(n===`cbcs`?{key:e,iv:r,data:i,algorithm:`AES-CBC`}:{key:e,iv:r,data:i,algorithm:`AES-CTR`})},L=async(e,t,{preventClose:n,onProgress:r,...i})=>{let a=`key`in i,o=new Uint8Array(ae(a?i.key:``)),s=async e=>I(o,e),c=a?s:i.transformSample;await F(e,t,{preventClose:n,onProgress:r,transformSample:c})},R=async(e,t,n)=>{let r=await(0,u.stat)(e),i=(0,l.createReadStream)(e,{highWaterMark:1024*1024*10}),a=d.Readable.toWeb(i),o=(0,l.createWriteStream)(t),s=d.Writable.toWeb(o);console.time(`
3
- Done!`),await L(a,s,{key:`key`in n?n.key:``,keyId:n.keyId,onProgress:e=>{process.stdout.write(`\rDecrypting... [${e}/${r.size}]`),e===r.size&&process.stdout.write(`
4
- `)}}),console.timeEnd(`
5
- Done!`)};var z=`shifro`,B=`0.1.2`,V=`A lightweight, dependency-free MP4 decrypter`,H=[`dist`],U={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 --minify`,typecheck:`tsc --noEmit`,test:`vitest`,prepublishOnly:`npm run build`},W={shifro:`./dist/cli.cjs`},oe=`module`,G=`./dist/shifro.js`,K=`./dist/shifro.d.ts`,q={".":{require:{types:`./dist/shifro.d.cts`,default:`./dist/shifro.cjs`},import:{types:`./dist/shifro.d.ts`,default:`./dist/shifro.js`}}},se={type:`git`,url:`git+https://github.com/streamyx-labs/shifro.git`},ce=[`mp4`,`mpeg`,`cenc`,`cbcs`,`decrypt`,`decryption`,`decrypter`,`decryptor`,`dash`,`drm`,`mp4decrypt`,`mp4box`,`shaka`,`shaka-packager`],le={url:`https://github.com/streamyx-labs/shifro/issues`,email:`vitalygashkov@vk.com`},ue=`Vitaly Gashkov <vitalygashkov@vk.com>`,de=`AGPL-3.0`,J=`README.md`,fe=[{type:`individual`,url:`https://boosty.to/vitalygashkov`},{type:`patreon`,url:`https://www.patreon.com/vitalygashkov`}],pe={node:`20 || 21 || 22 || 23`},me={"@types/node":`^22.15.3`,tsdown:`^0.11.0-beta.3`,typescript:`^5.8.3`,vitest:`^3.1.2`},he={name:z,version:B,description:V,files:H,scripts:U,bin:W,type:oe,main:G,types:K,exports:q,repository:se,keywords:ce,bugs:le,author:ue,license:de,readmeFilename:J,funding:fe,engines:pe,devDependencies:me};const Y=(0,c.parseArgs)({allowPositionals:!0,strict:!1,options:{key:{short:`k`,type:`string`,multiple:!0},help:{short:`h`,type:`boolean`}}}),ge=Array.isArray(Y.values.key)?Y.values.key:[],[X,Z]=ge[0]?.split(`:`)??[],[Q,$]=Y.positionals;(async()=>{Q&&$&&X&&Z?await R(Q,$,{key:Z,keyId:X}):console.log(`
6
- shifro version ${he.version}
7
- (c) 2024 Vitaly Gashkov <vitalygashkov@vk.com>
8
-
9
- Usage: shifro [options] <input> <output>
10
-
11
- Options:
12
- --key <id>:<k>
13
- <id> is either a track ID in decimal or a 128-bit KID in hex,
14
- <k> is a 128-bit key in hex
15
- (several --key options can be used, one for each track or KID)
16
- `.trim())})();
package/dist/shifro.cjs DELETED
@@ -1 +0,0 @@
1
- "use strict";const e=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)},t=e=>{let t=e.buffer,n=new DataView(t,e.byteOffset,e.byteLength);return n},n=(e,t=0,n)=>new Uint8Array(e.buffer,e.byteOffset+t,n);var r=class extends Error{constructor(e,t,n,r){super(r),this.severity=e,this.category=t,this.code=n,this.name=`DataViewReaderError`}},i=class{position=0;constructor(e,n=`BE`){this.dataView=t(e),this.littleEndian=n===`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 r(`CRITICAL`,`MEDIA`,`JS_INTEGER_OVERFLOW`,`64-bit integer overflow`);return this.position+=8,t*2**32+e}readBytes(e){this.checkBounds(e);let t=n(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 r(`CRITICAL`,`MEDIA`,`BUFFER_READ_OUT_OF_BOUNDS`,`Read operation out of bounds`)}};let a=function(e){return e[e.BASIC_BOX=0]=`BASIC_BOX`,e[e.FULL_BOX=1]=`FULL_BOX`,e}({});var o=class e{headers=new Map;boxDefinitions=new Map;done=!1;box(t,n){let r=e.typeFromString(t);return this.headers.set(r,a.BASIC_BOX),this.boxDefinitions.set(r,n),this}fullBox(t,n){let r=e.typeFromString(t);return this.headers.set(r,a.FULL_BOX),this.boxDefinitions.set(r,n),this}stop(){this.done=!0}parse(e,t=!1,n=!1){let r=new i(e);for(this.done=!1;r.hasMoreData()&&!this.done;)this.parseNext(0,r,t,n)}parseNext(t,n,r,o){let s=n.getPosition();if(o&&s+8>n.getLength()){this.done=!0;return}let c=n.readUint32(),l=n.readUint32(),u=e.typeToString(l),d=!1;switch(c){case 0:c=n.getLength()-s;break;case 1:if(o&&n.getPosition()+8>n.getLength()){this.done=!0;return}c=Number(n.readUint64()),d=!0;break}let f=this.boxDefinitions.get(l);if(f){let e=null,p=null;if(this.headers.get(l)===a.FULL_BOX){if(o&&n.getPosition()+4>n.getLength()){this.done=!0;return}let t=n.readUint32();e=t>>>24,p=t&16777215}let m=s+c;if(r&&m>n.getLength()&&(m=n.getLength()),o&&m>n.getLength()){this.done=!0;return}let h=m-n.getPosition(),g=h>0?n.readBytes(h):new Uint8Array,_=new i(g),v={name:u,parser:this,partialOkay:!!r,version:e,flags:p,reader:_,size:c,start:s+t,has64BitSize:d};f(v)}else{let e=Math.min(s+c-n.getPosition(),n.getLength()-n.getPosition());n.skip(e)}}static children(t){let n=e.headerSize(t);for(;t.reader.hasMoreData()&&!t.parser.done;)t.parser.parseNext(t.start+n,t.reader,t.partialOkay)}static sampleDescription(t){let n=e.headerSize(t),r=t.reader.readUint32();for(let e=0;e<r&&(t.parser.parseNext(t.start+n,t.reader,t.partialOkay),!t.parser.done);e++);}static visualSampleEntry(t){let n=e.headerSize(t);for(t.reader.skip(78);t.reader.hasMoreData()&&!t.parser.done;)t.parser.parseNext(t.start+n,t.reader,t.partialOkay)}static audioSampleEntry(t){let n=e.headerSize(t);t.reader.skip(8);let r=t.reader.readUint16();for(t.reader.skip(6),r===2?t.reader.skip(48):t.reader.skip(12),r===1&&t.reader.skip(16);t.reader.hasMoreData()&&!t.parser.done;)t.parser.parseNext(t.start+n,t.reader,t.partialOkay)}static allData(e){return t=>{let n=t.reader.getLength()-t.reader.getPosition();e(t.reader.readBytes(n))}}static typeFromString(e){if(e.length!==4)throw Error(`MP4 box names must be 4 characters long`);let t=0;for(let n of e)t=t<<8|n.charCodeAt(0);return t}static typeToString(e){return String.fromCharCode(e>>24&255,e>>16&255,e>>8&255,e&255)}static headerSize(e){let t=8,n=e.has64BitSize?8:0,r=e.flags===null?0:4;return t+n+r}};const s=(e,t)=>{if(t&&t&1){let t=e.readUint8(),n=e.readUint8(),r=e.readBytes(16)}let n=e.readUint32(),r=[];for(let i=0;i<n;i++){let n=8,i=new Uint8Array(16);i.set(e.readBytes(n));let a={iv:i,subsamples:[]},o=t&&t&2;if(o){let t=e.readUint16();for(let n=0;n<t;n++){let t=e.readUint16(),n=e.readUint32();a.subsamples.push({bytesOfClearData:t,bytesOfEncryptedData:n})}}r.push(a)}return{samples:r}},c=(e,t,n)=>{let r=e.readUint32(),i=[],a=null;t&1&&(a=e.readInt32()),t&4&&e.skip(4);for(let a=0;a<r;a++){let r={};t&256&&(r.duration=e.readUint32()),t&512&&(r.size=e.readUint32()),t&1024&&e.skip(4),t&2048&&(r.compositionTimeOffset=n==0?e.readUint32():e.readInt32()),i.push(r)}return{samples:i,dataOffset:a}},l=(e,t)=>{let n,r,i,a,o=e.readUint32();return t&1&&(i=e.readUint64()),t&2&&(a=e.readUint32()),t&8&&(n=e.readUint32()),t&16&&(r=e.readUint32()),{trackId:o,defaultSampleDuration:n,defaultSampleSize:r,baseDataOffset:i,sampleDescriptionIndex:a}},u=(e,t,n)=>{if(t.length===0)return-1;let r=e.length-t.length+1;for(let i=n;i<r;i++){let n=!0;for(let r=0;r<t.length;r++)if(e[i+r]!==t[r]){n=!1;break}if(n)return i}return-1},d=(e,t,n)=>{let r=new TextEncoder,i=r.encode(t),a=r.encode(n);if(i.length!==a.length)throw Error(`Original and replacement must have the same byte length`);let o=0;do o=u(e,i,0),o!==-1&&e.set(a,o);while(o!==-1)},f=(e,t,n=0)=>{let r=new TextEncoder,i;return i=typeof t==`string`?r.encode(t):typeof t==`number`?new Uint8Array([t]):t,i.length===0?!0:e.length<i.length?!1:u(e,i,n)!==-1},p=(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},m=(e,t,n=0,r=0,i=e.length)=>{n=Math.max(0,Math.min(n,t.length)),r=Math.max(0,Math.min(r,e.length)),i=Math.min(i,e.length),i<r&&(i=r);let a=Math.min(i-r,t.length-n);return a<=0?0:(t.set(e.subarray(r,r+a),n),a)},h=(e,t,n=0,r)=>{let i=new TextEncoder,a=i.encode(t),o=Math.max(0,Math.min(n,e.length)),s=e.length-o,c=a.length;return typeof r==`number`&&(c=Math.min(c,r)),c=Math.min(c,s),c<=0?0:(e.set(a.subarray(0,c),o),c)},g=e=>Array.from(e).map(e=>e.toString(16).padStart(2,`0`)).join(``),_=e=>{let t=Array.from(e,e=>String.fromCodePoint(e)).join(``);return btoa(t)},v=e=>e.match(/.{1,2}/g)?.map(e=>parseInt(e,16))??[],y=e=>{e.readUint8(),e.readUint8(),e.readUint8(),e.readUint8();let t=g(e.readBytes(16));return{defaultKID:t}},b=e=>{let t=`edef8ba979d64acea3c827dcd51d21ed`,n=e.version??0;if(n!==0&&n!==1)throw Error(`PSSH version can only be 0 or 1`);let r=e.reader.readBytes(16),i=g(r),a=[i.slice(0,8),i.slice(8,12),i.slice(12,16),i.slice(16,20),i.slice(20)].join(`-`),o=e.reader.getDataView(),s=new Uint8Array(o.buffer,o.byteOffset-12,e.size),c=_(s),l=[];if(n>=1){let t=e.reader.readUint32();for(let n=0;n<t;n++)l.push(g(e.reader.readBytes(16)))}let u=e.reader.readUint32(),d=e.reader.readBytes(u),f=i===t;return{version:n,systemId:a,pssh:c,keyIds:l,systemData:d}},x=e=>{try{let t=!1,n=!1;return new o().box(`moov`,()=>{t=!0}).box(`moof`,()=>{n=!0}).parse(e,!0,!0),t&&!n}catch(e){return console.log(e),!1}},S=e=>{let t=null;return new o().box(`moov`,o.children).box(`trak`,o.children).box(`mdia`,o.children).box(`minf`,o.children).box(`stbl`,o.children).fullBox(`stsd`,o.sampleDescription).box(`encv`,o.visualSampleEntry).box(`enca`,o.audioSampleEntry).box(`sinf`,o.children).box(`frma`,e=>{let n=e.reader.readBytes(4);t=new TextDecoder().decode(n)}).parse(e,!0,!0),t},C=async e=>{let t=S(e);if(d(e,`sinf`,`skip`),d(e,`pssh`,`skip`),t)d(e,`encv`,t),d(e,`enca`,t);else{console.warn(`Could not detect original codec, using fallback values`);let t=f(e,`encv`)?`video`:`audio`;t===`video`?d(e,`encv`,`avc1`):d(e,`enca`,`mp4a`)}return e},w=e=>{let t={schemeType:``,defaultKID:``,psshList:[]};return new o().box(`moov`,o.children).box(`trak`,o.children).box(`mdia`,o.children).box(`minf`,o.children).box(`stbl`,o.children).fullBox(`stsd`,o.sampleDescription).fullBox(`pssh`,e=>t.psshList.push(b(e))).box(`encv`,o.visualSampleEntry).box(`enca`,o.audioSampleEntry).box(`sinf`,o.children).box(`schi`,o.children).fullBox(`schm`,e=>{t.schemeType=new TextDecoder().decode(e.reader.readBytes(4))}).fullBox(`tenc`,e=>{let{defaultKID:n}=y(e.reader);t.defaultKID=n}).parse(e,!1,!0),t},T=async(e,t)=>{let n=x(e);if(n)return C(e);let r,i,a,u;if(new o().box(`moof`,o.children).box(`traf`,o.children).fullBox(`tfhd`,e=>{a=l(e.reader,e.flags)}).fullBox(`trun`,e=>{i=c(e.reader,e.flags,e.version)}).fullBox(`senc`,e=>{r=s(e.reader,e.flags)}).parse(e,!0,!0),u=i.dataOffset,r.samples.length!==i.samples.length)throw Error(`sample count mismatch: trun has ${i.samples.length}, senc has ${r.samples.length}`);let d=0,f=0;for(let n=0;n<r.samples.length;n++){let o=r.samples[n],s=i.samples[n],c=i.samples[n].size||a.defaultSampleSize||0;o.subsamples.length||o.subsamples.push({bytesOfClearData:0,bytesOfEncryptedData:c});let l=o.subsamples.some(e=>e.bytesOfEncryptedData>0);if(l){let n=0,r=[];for(let t of o.subsamples){if(n+=t.bytesOfClearData,t.bytesOfEncryptedData>0){let i=e.subarray(u+d+n,u+d+n+t.bytesOfEncryptedData);r.push(i)}n+=t.bytesOfEncryptedData}let i=p(r),a={iv:o.iv,data:i,timestamp:f},s=await t(a);if(s){n=0;let t=0,r=[];for(let i of o.subsamples){if(i.bytesOfClearData>0){let t=e.subarray(u+d+n,u+d+n+i.bytesOfClearData);r.push(t),n+=i.bytesOfClearData}if(i.bytesOfEncryptedData>0){let e=s.subarray(t,t+i.bytesOfEncryptedData);r.push(e),t+=i.bytesOfEncryptedData,n+=i.bytesOfEncryptedData}}let i=p(r);m(i,e,u+d)}}d+=c,f+=s.duration||a.defaultSampleDuration||0}return new o().box(`moof`,o.children).box(`traf`,o.children).box(`senc`,t=>{let n=`skip`,r=t.start+n.length;h(e,n,r)}).parse(e,!0,!0),e},E=e=>{let t=null;new o().box(`moov`,e=>{t=e.start+e.size}).parse(e,!0,!0);let n=t?e.subarray(0,t):null;return{moovEnd:t,init:n}},D=e=>{let t=null,n=null;new o().box(`moof`,e=>{t===null&&(t=e.start)}).box(`mdat`,e=>{n===null&&(n=e.start+e.size)}).parse(e,!0,!0);let r=t!==null&&n!==null,i=r?e.subarray(t,n):null;return{segment:i,moofStart:t,mdatEnd:n}};var O=class{buffer=new Uint8Array;isProcessingInit=!0;scheme=null;processedBytes=0;constructor(e){this.options=e}async transform(e,t){try{this.buffer=p([this.buffer,e]);let n=!0;for(;n;){if(this.isProcessingInit){let{init:e,moovEnd:n}=E(this.buffer);if(!n||this.buffer.length<n)break;if(e&&x(e)){let r=w(e);this.scheme=r.schemeType;let i=await C(e);t.enqueue(i),this.updateProgress(i.byteLength),this.buffer=this.buffer.subarray(n),this.isProcessingInit=!1;continue}}let{moofStart:e,mdatEnd:r,segment:i}=D(this.buffer);if(e===null||r===null||!i||this.buffer.length<r)break;let a=e=>(e.encryptionScheme=this.scheme,this.options.transformSample(e)),o=await T(i,a);t.enqueue(o),this.updateProgress(o.byteLength),this.buffer=this.buffer.subarray(r),n=this.buffer.length>=8}}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)}};const k=async(e,t,{transformSample:n,onProgress:r,preventClose:i})=>{let a=new O({transformSample:n,onProgress:r}),o=new TransformStream({transform:(e,t)=>a.transform(e,t),flush:e=>a.flush(e)});await e.pipeThrough(o).pipeTo(t,{preventClose:i})},A=async(t,n)=>{let r=n.encryptionScheme,i=n.iv,a=n.data;return e(r===`cbcs`?{key:t,iv:i,data:a,algorithm:`AES-CBC`}:{key:t,iv:i,data:a,algorithm:`AES-CTR`})},j=async(e,t)=>{let n=`key`in t,r=new Uint8Array(v(n?t.key:``)),i=async e=>A(r,{encryptionScheme:`encryptionScheme`in t?t.encryptionScheme:void 0,...e}),a=n?i:t.transformSample;return T(e,a)},M=async(e,t,{preventClose:n,onProgress:r,...i})=>{let a=`key`in i,o=new Uint8Array(v(a?i.key:``)),s=async e=>A(o,e),c=a?s:i.transformSample;await k(e,t,{preventClose:n,onProgress:r,transformSample:c})};exports.decryptSegment=j,exports.decryptStream=M,exports.decryptWithKey=A;
package/dist/shifro.d.cts DELETED
@@ -1,40 +0,0 @@
1
- //#region lib/process.d.ts
2
- type EncryptionScheme = 'cenc' | 'cbcs';
3
- type TransformSampleParams = {
4
- encryptionScheme?: EncryptionScheme;
5
- data: Uint8Array;
6
- iv: Uint8Array;
7
- timestamp: number;
8
- };
9
- type TransformSampleFn = (params: TransformSampleParams) => Promise<Uint8Array | null>;
10
-
11
- //#endregion
12
- //#region lib/stream.d.ts
13
- interface ProcessStreamOptions {
14
- transformSample: TransformSampleFn;
15
- onProgress?: (bytesProcessed: number) => void;
16
- preventClose?: boolean;
17
- }
18
-
19
- //#endregion
20
- //#region shifro.d.ts
21
- declare const decryptWithKey: (key: Uint8Array, params: TransformSampleParams) => Promise<Uint8Array<ArrayBuffer>>;
22
- type DecryptWithKey = {
23
- key: string;
24
- keyId?: string;
25
- encryptionScheme?: EncryptionScheme;
26
- };
27
- type DecryptWithCallback = {
28
- keyId?: string;
29
- transformSample: (params: TransformSampleParams) => Promise<Uint8Array | null>;
30
- };
31
- type DecryptParams = DecryptWithKey | DecryptWithCallback;
32
- declare const decryptSegment: (segment: Uint8Array, params: DecryptParams) => Promise<Uint8Array<ArrayBufferLike>>;
33
- declare const decryptStream: (readable: ReadableStream, writable: WritableStream, {
34
- preventClose,
35
- onProgress,
36
- ...params
37
- }: DecryptParams & Omit<ProcessStreamOptions, "transformSample">) => Promise<void>;
38
-
39
- //#endregion
40
- export { DecryptParams, ProcessStreamOptions, TransformSampleParams, decryptSegment, decryptStream, decryptWithKey };