shifro 0.1.2 → 0.2.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 +47 -46
- package/dist/cli.cjs +11 -12
- package/dist/shifro.cjs +1066 -1
- package/dist/shifro.d.cts +65 -4
- package/dist/shifro.d.ts +65 -4
- package/dist/shifro.js +1033 -1
- package/package.json +12 -13
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# shifro
|
|
2
2
|
|
|
3
|
-
A lightweight
|
|
3
|
+
A lightweight library for decrypting MP4 files, directly in the browser
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -8,7 +8,7 @@ A lightweight, dependency-free MP4 decrypter
|
|
|
8
8
|
- **Small** size (under 15kB without types, minified)
|
|
9
9
|
- **Command-line** interface
|
|
10
10
|
- **Segment-by-segment processing** with JavaScript library
|
|
11
|
-
- **Custom handler** for
|
|
11
|
+
- **Custom handler** for samples processing
|
|
12
12
|
|
|
13
13
|
## Prerequisites
|
|
14
14
|
|
|
@@ -32,61 +32,62 @@ npm install -g shifro
|
|
|
32
32
|
|
|
33
33
|
### Library
|
|
34
34
|
|
|
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
35
|
#### Decrypting file using browser's Web Streams API
|
|
66
36
|
|
|
67
37
|
```js
|
|
68
|
-
import {
|
|
38
|
+
import { Input, StreamSource, Output, StreamTarget, Decryption } from 'shifro';
|
|
69
39
|
|
|
70
|
-
const
|
|
71
|
-
const keyId = '100b6c20940f779a4589152b57d2dacb';
|
|
72
|
-
|
|
73
|
-
const waitForInput = async () =>
|
|
40
|
+
const selectFile = async () =>
|
|
74
41
|
new Promise<File>((resolve) => {
|
|
75
42
|
const input = document.querySelector<HTMLInputElement>('#input')!;
|
|
76
43
|
input.addEventListener('change', () => resolve(input.files![0]));
|
|
77
44
|
});
|
|
78
45
|
|
|
79
|
-
|
|
80
|
-
const
|
|
46
|
+
async function main() {
|
|
47
|
+
const inputFile = await selectFile();
|
|
48
|
+
const inputStream = inputFile.stream();
|
|
49
|
+
|
|
50
|
+
const outputFileHandle = window.showSaveFilePicker({ suggestedName: 'output.mp4', startIn: 'downloads' });
|
|
51
|
+
const outputStream = await outputFileHandle.createWritable();
|
|
52
|
+
|
|
53
|
+
const input = new Input({ source: new StreamSource(inputStream) });
|
|
54
|
+
const output = new Output({ target: new StreamTarget(outputStream) });
|
|
55
|
+
const decryption = await Decryption.init({
|
|
56
|
+
input,
|
|
57
|
+
output,
|
|
58
|
+
keys: [
|
|
59
|
+
{
|
|
60
|
+
kid: 'eb676abbcb345e96bbcf616630f1a3da',
|
|
61
|
+
key: '100b6c20940f779a4589152b57d2dacb',
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
onProgress: (progress) => console.log(`Decrypting... ${progress}`),
|
|
65
|
+
});
|
|
66
|
+
await decryption.execute();
|
|
67
|
+
}
|
|
68
|
+
```
|
|
81
69
|
|
|
82
|
-
|
|
83
|
-
const outputStream = await outputFileHandle.createWritable();
|
|
70
|
+
#### Decrypting file using Node.js streams
|
|
84
71
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
});
|
|
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
|
+
await decryption.execute();
|
|
90
|
+
}
|
|
90
91
|
```
|
|
91
92
|
|
|
92
93
|
#### Segment-by-segment decryption
|
package/dist/cli.cjs
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
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
|
|
4
|
-
`)}}),console.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
(c) 2024 Vitaly Gashkov <vitalygashkov@vk.com>
|
|
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}
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
Usage: ${J.name} [options] <input> <output>
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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)
|
|
16
15
|
`.trim())})();
|