UNPKG

shifro

Version:

A lightweight, dependency-free MP4 decrypter

1 lines 12.9 kB
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})};export{j as decryptSegment,M as decryptStream,A as decryptWithKey};