Skip to content

azot-labs/shifro

Repository files navigation

shifro

A lightweight library for decrypting MP4 files, directly in the browser or via CLI on your machine.

This tool is a small wrapper around the Mediabunny library with slight API changes and command-line support.

Usage

CLI

Node.js is required (or any other runtime that has compatibility with Node.js API, like Bun).

Run npm install -g shifro to install the command-line tool globally.

shifro --key eb676abbcb345e96bbcf616630f1a3da:100b6c20940f779a4589152b57d2dacb ./input.mp4 ./output.mp4

You can also use npx shifro ... to run the CLI tool without installing it globally

This CLI can be used as an alternative to the mp4decrypt or shaka-packager.

Library

Run npm install shifro to install the library as dependency for your project.

Decrypting file using Node.js streams

import { Decryption, FilePathSource, FilePathTarget, Input, Output } from 'shifro';
import type { Key, KeyId } from 'shifro';

async function decrypt() {
  const decryption = await Decryption.init({
    input: new Input({
      source: new FilePathSource('./input.mp4'),
      keys: new Map<KeyId, Key>([
        ['4d97930a3d7b55fa81d0028653f5e499', '429ec76475e7a952d224d8ef867f12b6'],
        ['d21373c0b8ab5ba9954742bcdfb5f48b', '150a6c7d7dee6a91b74dccfce5b31928'],
      ]),
      // Optinally, you can use this callback to handle encryption information (like key ID and PSSH boxes)
      handleEncryptionInfo: ({ keyId, psshBoxes }) => {
        console.group(`Key ID: ${keyId}`);
        for (const psshBox of psshBoxes) {
          console.log(`PSSH box: ${psshBox.systemId} (${psshBox.data.length} bytes)`);
        }
        console.groupEnd();
      },
    }),
    output: new Output({ target: new FilePathTarget('./output.mp4') }),
  });

  decryption.onProgress = (progress) =>
    process.stdout.write(`\rDecrypting... [${Math.round(progress * 100)}%]`);

  await decryption.execute();
}

Decrypting file using browser's Web Streams API

import { Decryption, Input, Output, ReadableStreamSource, StreamTarget } from 'shifro';
import type { Key, KeyId } from 'shifro';

async function decryptFromBrowser() {
  const handleFileSelect = async () => {
    return new Promise<File>((resolve) => {
      const input = document.querySelector<HTMLInputElement>('#input')!;
      input.addEventListener('change', () => resolve(input.files![0]));
    });
  };

  const inputFile = await handleFileSelect();
  const inputReadableStream = inputFile.stream();

  const outputFileHandle = window.showSaveFilePicker({
    suggestedName: 'output.mp4',
    startIn: 'downloads',
  });
  const outputWritableStream = await outputFileHandle.createWritable();

  const decryption = await Decryption.init({
    input: new Input({
      source: new ReadableStreamSource(inputReadableStream),
      keys: new Map<KeyId, Key>([
        ['4d97930a3d7b55fa81d0028653f5e499', '429ec76475e7a952d224d8ef867f12b6'],
        ['d21373c0b8ab5ba9954742bcdfb5f48b', '150a6c7d7dee6a91b74dccfce5b31928'],
      ]),
    }),
    output: new Output({ target: new StreamTarget(outputWritableStream) }),
  });

  decryption.onProgress = (progress) =>
    console.log(`Decrypting... [${Math.round(progress * 100)}%]`);

  await decryption.execute();
}

Credits