Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@remotion/media-parser: Get tracks and packets, apply rotation, new API signature #4172

Merged
merged 55 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
c26efef
start working on browser renderer
JonnyBurger Mar 9, 2024
395e714
re-encode video
JonnyBurger Mar 9, 2024
ddcbcf1
extract into load mp4 file
JonnyBurger Mar 9, 2024
f51fc64
Update re-encode-video.ts
JonnyBurger Mar 9, 2024
1479569
cleaner code
JonnyBurger Mar 9, 2024
a407d4c
eslint + prettier
JonnyBurger Mar 9, 2024
0ef6513
split up into more files
JonnyBurger Mar 9, 2024
cf56b39
Update re-encode-video.ts
JonnyBurger Mar 9, 2024
b7b9c4c
cool
JonnyBurger Mar 9, 2024
0e46b89
progress
JonnyBurger Mar 9, 2024
106564d
simplify
JonnyBurger Mar 9, 2024
b8b2c28
merge
JonnyBurger Aug 7, 2024
47a299e
encoder
JonnyBurger Aug 7, 2024
0e53dad
can skip video data
JonnyBurger Aug 7, 2024
0374624
stream samples
JonnyBurger Aug 7, 2024
5ceaa38
parse stsz
JonnyBurger Aug 7, 2024
0e2bcc6
parse stco
JonnyBurger Aug 7, 2024
ec296c3
new signature
JonnyBurger Aug 7, 2024
28a9f84
some tracks progress
JonnyBurger Aug 7, 2024
0b047e3
more codecs
JonnyBurger Aug 7, 2024
6fc3a62
nice
JonnyBurger Aug 7, 2024
1730d99
do something cool with it
JonnyBurger Aug 7, 2024
b4c3d05
alright
JonnyBurger Aug 7, 2024
9bcdf79
samples
JonnyBurger Aug 7, 2024
abce724
Update mdat.ts
JonnyBurger Aug 7, 2024
9dbfc6e
Merge branch 'main' into browser-renderer
JonnyBurger Aug 8, 2024
25f27df
coool
JonnyBurger Aug 8, 2024
2c815fe
Update package.json
JonnyBurger Aug 8, 2024
eaf383c
video parsers
JonnyBurger Aug 8, 2024
79cd8dd
lolll
JonnyBurger Aug 8, 2024
03acde0
get tracks
JonnyBurger Aug 8, 2024
515293a
parse void boxes
JonnyBurger Aug 8, 2024
242547b
don't handle mdat yet
JonnyBurger Aug 8, 2024
b0fa467
audioTracks / videoTracks
JonnyBurger Aug 8, 2024
ce83bcd
literally sick
JonnyBurger Aug 8, 2024
9e6287a
get timescale
JonnyBurger Aug 8, 2024
950f61b
dts and delta
JonnyBurger Aug 8, 2024
8a5cb75
parse ctts
JonnyBurger Aug 8, 2024
6ad2717
awesome, decoder works
JonnyBurger Aug 8, 2024
ac41892
coool
JonnyBurger Aug 9, 2024
100567a
stream samples
JonnyBurger Aug 9, 2024
22b9c72
as number
JonnyBurger Aug 9, 2024
41a47fd
get audio codec
JonnyBurger Aug 9, 2024
6f135cf
mp4a.6b
JonnyBurger Aug 9, 2024
c755bfa
support hvcc codec strings
JonnyBurger Aug 9, 2024
718ad22
detect audio codecs
JonnyBurger Aug 9, 2024
f44e50a
change API
JonnyBurger Aug 9, 2024
4221e1d
add metadata
JonnyBurger Aug 9, 2024
c758a8b
fix rotation
JonnyBurger Aug 9, 2024
ade7eb3
expanded dimensions
JonnyBurger Aug 9, 2024
680fcfd
return rotation
JonnyBurger Aug 9, 2024
6fec1df
unrotated dimensions
JonnyBurger Aug 9, 2024
27e01c8
ignore mp4box types
JonnyBurger Aug 9, 2024
487d4f9
Update TableOfContents.tsx
JonnyBurger Aug 9, 2024
3cb888a
Delete boxes.json
JonnyBurger Aug 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/browser-renderer/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@jonny"
}
18 changes: 18 additions & 0 deletions packages/browser-renderer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# @remotion/browser-renderer

Private experimental package

[![NPM Downloads](https://img.shields.io/npm/dm/@remotion/browser-renderer.svg?style=flat&color=black&label=Downloads)](https://npmcharts.com/compare/@remotion/browser-renderer?minimal=true)

## Installation

```bash
npm install @remotion/browser-renderer --save-exact
```

When installing a Remotion package, make sure to align the version of all `remotion` and `@remotion/*` packages to the same version.
Remove the `^` character from the version number to use the exact version.

## Usage

This is an internal package and has no documentation.
34 changes: 34 additions & 0 deletions packages/browser-renderer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@remotion/browser-renderer",
"version": "4.0.194",
"main": "dist/index.js",
"sideEffects": false,
"repository": {
"url": "https://github.com/remotion-dev/remotion/tree/main/packages/browser-renderer"
},
"bugs": {
"url": "https://github.com/remotion-dev/remotion/issues"
},
"scripts": {
"formatting": "prettier src --check",
"lint": "eslint src --ext ts,tsx",
"build": "tsc -d",
"watch": "tsc -w"
},
"files": [
"dist"
],
"author": "Jonny Burger <[email protected]>",
"license": "SEE LICENSE IN LICENSE.md",
"dependencies": {
"mp4box": "0.5.2"
},
"peerDependencies": {},
"devDependencies": {
"@remotion/media-parser": "workspace:*",
"@types/dom-webcodecs": "0.1.11"
},
"keywords": [],
"private": true,
"description": "Private experimental package"
}
41 changes: 41 additions & 0 deletions packages/browser-renderer/src/create-decoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
export const createDecoder = ({
onProgress,
onFrame,
}: {
onProgress: (decoded: number) => void;
onFrame: (frame: VideoFrame, keyframe: boolean) => void;
}) => {
let decodedFrames = 0;
let nextKeyFrameTimestamp = 0;

const decoder = new VideoDecoder({
async output(inputFrame) {
const bitmap = await createImageBitmap(inputFrame);

const keyFrameEveryHowManySeconds = 2;
let keyFrame = false;
if (inputFrame.timestamp >= nextKeyFrameTimestamp) {
keyFrame = true;
nextKeyFrameTimestamp =
inputFrame.timestamp + keyFrameEveryHowManySeconds * 1e6;
}

const outputFrame = new VideoFrame(bitmap, {
timestamp: inputFrame.timestamp,
duration: inputFrame.duration as number,
});

onFrame(outputFrame, keyFrame);

inputFrame.close();

decodedFrames++;
onProgress(decodedFrames);
},
error(error) {
console.error(error);
},
});

return {decoder};
};
64 changes: 64 additions & 0 deletions packages/browser-renderer/src/create-encoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {createFile} from 'mp4box';

const timescale = 90000;

export const createEncoder = ({
width,
height,
onProgress,
}: {
width: number;
height: number;
onProgress: (encoded: number) => void;
}) => {
let trackID: number | null = null;

let encodedFrames = 0;

const outputMp4 = createFile();

const encoder = new VideoEncoder({
output(chunk, metadata) {
const uint8 = new Uint8Array(chunk.byteLength);
chunk.copyTo(uint8);

if (trackID === null) {
trackID = outputMp4.addTrack({
width,
height,
timescale,
avcDecoderConfigRecord: (
metadata!.decoderConfig as VideoDecoderConfig
).description,
});
}

if (chunk.duration === null) {
throw new Error('No duration in the chunk');
}

const sampleDuration = chunk.duration / (1_000_000 / timescale);

outputMp4.addSample(trackID, uint8, {
duration: sampleDuration,
is_sync: chunk.type === 'key',
});

encodedFrames++;
onProgress(encodedFrames);
},
error(error) {
console.error(error);
},
});

encoder.configure({
codec: 'avc1.4d0034',
width,
height,
hardwareAcceleration: 'prefer-hardware',
bitrate: 20_000_000,
});

return {encoder, outputMp4};
};
24 changes: 24 additions & 0 deletions packages/browser-renderer/src/get-description.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type {MP4File, MP4MediaTrack} from 'mp4box';
import {DataStream} from 'mp4box';

export const getDescription = ({
mp4File,
track,
}: {
mp4File: MP4File;
track: MP4MediaTrack;
}) => {
const trak = mp4File.getTrackById(track.id);
for (const entry of trak.mdia.minf.stbl.stsd.entries) {
if (entry.avcC || entry.hvcC) {
const stream = new DataStream(undefined, 0, DataStream.BIG_ENDIAN);
if (entry.avcC) {
entry.avcC.write(stream);
} else if (entry.hvcC) {
entry.hvcC.write(stream);
}

return new Uint8Array(stream.buffer, 8); // Remove the box header.
}
}
};
31 changes: 31 additions & 0 deletions packages/browser-renderer/src/get-samples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type {MP4File, MP4MediaTrack, Sample} from 'mp4box';

export const getSamples = ({
mp4File,
track,
}: {
mp4File: MP4File;
track: MP4MediaTrack;
}) => {
mp4File.setExtractionOptions(track.id, null, {
nbSamples: Infinity,
});

return new Promise<Sample[]>((resolve, reject) => {
mp4File.onSamples = (track_id, _ref, samples) => {
if (track_id === track.id) {
resolve(samples);
mp4File.onSamples = undefined;
mp4File.onError = undefined;
}
};

mp4File.onError = (e) => {
reject(e);
mp4File.onSamples = undefined;
mp4File.onError = undefined;
};

mp4File.start();
});
};
2 changes: 2 additions & 0 deletions packages/browser-renderer/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export {reencodeVideo} from './reencode-video';
export {parseVideo} from './video-parser';
48 changes: 48 additions & 0 deletions packages/browser-renderer/src/load-mp4-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type {MP4ArrayBuffer, MP4File, MP4Info} from 'mp4box';
import {createFile} from 'mp4box';

type ReturnType = {
mp4File: MP4File;
info: MP4Info;
};

const readFile = (file: File): Promise<MP4ArrayBuffer> => {
return new Promise<MP4ArrayBuffer>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
(reader.result as MP4ArrayBuffer).fileStart = 0;
resolve(reader.result as MP4ArrayBuffer);
reader.onerror = null;
reader.onload = null;
};

reader.onerror = (error) => {
reject(error);
reader.onerror = null;
reader.onload = null;
};

reader.readAsArrayBuffer(file);
});
};

export const loadMp4File = async (file: File) => {
const mp4File = createFile();

const arrayBuffer = await readFile(file);

const prom = new Promise<ReturnType>((resolve, reject) => {
mp4File.onError = (error) => {
reject(error);
};

mp4File.onReady = (info) => {
resolve({mp4File, info});
};
});

mp4File.appendBuffer(arrayBuffer);
mp4File.flush();

return prom;
};
66 changes: 66 additions & 0 deletions packages/browser-renderer/src/reencode-video.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {createDecoder} from './create-decoder';
import {createEncoder} from './create-encoder';
import {getDescription} from './get-description';
import {getSamples} from './get-samples';
import {loadMp4File} from './load-mp4-file';

export const reencodeVideo = async (file: File) => {
const {info, mp4File} = await loadMp4File(file);
const track = info.videoTracks[0];

if (!track) {
throw new Error('No video track found in the file');
}

const {encoder, outputMp4} = createEncoder({
width: track.track_width,
height: track.track_height,
onProgress: (encoded) => {
const encodingProgress = Math.round((100 * encoded) / track.nb_samples);
console.log(`Encoding frame ${encoded} (${encodingProgress}%)`);
},
});

const {decoder} = createDecoder({
onFrame: (frame, keyframe) => {
encoder.encode(frame, {keyFrame: keyframe});
frame.close();
},
onProgress: (decoded) => {
const decodingProgress = Math.round((100 * decoded) / track.nb_samples);
console.log(`Decoding frame ${decoded} (${decodingProgress}%)`);
},
});

decoder.configure({
codec: track.codec,
codedWidth: track.track_width,
codedHeight: track.track_height,
hardwareAcceleration: 'prefer-hardware',
description: getDescription({mp4File, track}),
});

const samples = await getSamples({mp4File, track});
console.log(samples);

for (const sample of samples) {
const duration = (sample.duration * 1_000_000) / sample.timescale;
const timestamp = (sample.cts * 1_000_000) / sample.timescale;

const chunk = new EncodedVideoChunk({
type: sample.is_sync ? 'key' : 'delta',
timestamp,
duration,
data: sample.data,
});

decoder.decode(chunk);
}

await decoder.flush();
await encoder.flush();
encoder.close();
decoder.close();

outputMp4.save('mp4box.mp4');
};
Loading
Loading