jupyterlabのwav用MIMEレンダラーを作成しました

概要

jupyterlab内でwavファイルが開けなかったのでチュートリアルを参考にMIMEレンダラーを作成しました。 正直wavファイルの場合は上記チュートリアルと全く同じ操作で作成できてしまいました。

プロジェクトの初期化

cookiecutterを使って雛形を生成します。

❯ cookiecutter https://github.com/jupyterlab/mimerender-cookiecutter-ts.git
author_name []: wrist
author_email []: stoicheia1986@gmail.com
extension_name [myextension]: jupyterlab-wav
viewer_name [My Viewer]: JupyterLab wav viewer
mimetype [application/vnd.my_organization.my_type]: audio/wav
mimetype_name [my_type]: wav
file_extension [.my_type]: .wav
Select data_format:
1 - string
2 - json
Choose from 1, 2 (1, 2) [1]: 1

Extensionのビルドとインストール

Extensionの作成にはyarnのバージョンが固定されたjlpmというjupyterlab付属のツールを用いて行います。 依存パッケージをインストールし、Extensionをビルドし、jupyterlabのextensionとしてインストールするためには下記を実行します。

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

ここで上記をローカル環境で試したところ、jupyterlabのバージョンが1.2.1の場合は現在のJupyterlabとはバージョンの互換性がないというエラーがでてしまいました(既に2.0.0以上を想定している模様です)。

このためjupyterlabを動作させるために使用しているdocker imageであるwrist/jupyterlab-customを更新したのですが、 jupyterlab-vimは2.x系に対応してなかったので代わりに https://github.com/jwkvam/jupyterlab-vim/pull/115 などを参照し、 @axlair/jupyterlab_vimをインストールしています。 なお、ローカルでdocker imageをビルドする際に当初jupyter lab buildを実行すると このissueと同じようにensure-max-old-space実行時にエラーが出てbuildできなくなりましたが、 osx上でDockerが使用するメモリを4096MBにしたところエラーが生じなくなりました。

コードの監視

jlpm run watchでextensionのコードに変更があるとすぐにrecompileしてくれるようになります。 またjupyterlabをjupyter lab --watchとして立ち上げるとその変更を監視しアプリケーションに取り込んでくれるようになります。 しかし手元ではwebpackがファイル監視に使用しているchokdairを見つけられないというエラーで動作しないため一旦諦めました。 なお、上記watchを施さなくてもjupyterlab自体をreloadするとコードに変更があった場合はbuildを促されるので大きな問題は生じませんでした。

※7/8追記: この監視がうまく行かない件ですがこのissueによればv2.1.3のバグとのことでv2.1.4にjupyterlabのバージョンを上げたら解決しました。

コードの構造

自動生成されたコードのsrc/index.tsを見ると主に3つのデータ構造を含んでいます。

  • OutputWidgetクラス
    • 指定したMIMEタイプのデータを受け取りHTML DOMノードの中にどのように描画するのかを扱うクラス
    • extensionのほとんどのロジックを含む
  • rendererFactoryオブジェクト
    • OutputWidgetクラスのインスタンスをどのようにアプリケーション内で生成するのかを扱うクラス
  • extensionオブジェクト
    • extensionのメインのエントリーポイントとなる部分
    • jupyterlabがextensionをロードする際に必要となるメタデータを書く

コードの編集

下記編集を実施します。

  • OutputWidgetのリネーム
    • src/index.ts内のOutputWidgetWavWidgetへとリネームします(2箇所)
  • extensionオブジェクトのfileTypesmodelNameにbase64エンコードを指定
    • デフォルトだとプレーンテキストとして読もうとするのでbase64エンコードされたものとして読むための指定を追記
    • fileTypefileFormat: 'base64'documentWidgetFactoryOptionsmodelName: 'base64'を追加する
  • レンダー方法の指定
    • WavWidgetクラスを編集
      • コンストラクタにaudioタグを追加するためのコードを追加
      • renderModelメソッド内でaudioタグのsrcを指定

大した量ではないためコード全体を下記に記します。

import { IRenderMime } from '@jupyterlab/rendermime-interfaces';



import { Widget } from '@lumino/widgets';

/**
 * The default mime type for the extension.
 */
const MIME_TYPE = 'audio/wav';

/**
 * The class name added to the extension.
 */
const CLASS_NAME = 'mimerenderer-wav';

/**
 * A widget for rendering wav.
 */
export class WavWidget extends Widget implements IRenderMime.IRenderer {
  /**
   * Construct a new output widget.
   */
  constructor(options: IRenderMime.IRendererOptions) {
    super();
    this._mimeType = options.mimeType;
    this.addClass(CLASS_NAME);
    /* 追加 */
    this._audio = document.createElement('audio');
    this._audio.setAttribute('controls', '');
    this.node.appendChild(this._audio);
  }

  /**
   * Render wav into this widget's node.
   */
  renderModel(model: IRenderMime.IMimeModel): Promise<void> {

    let data = model.data[this._mimeType] as string;
    /* 元コードを削除し下記を追加 */
    this._audio.src = `data:${MIME_TYPE};base64,${data}`

    return Promise.resolve();
  }

  private _audio: HTMLAudioElement;
  private _mimeType: string;
}

/**
 * A mime renderer factory for wav data.
 */
export const rendererFactory: IRenderMime.IRendererFactory = {
  safe: true,
  mimeTypes: [MIME_TYPE],
  createRenderer: options => new WavWidget(options)
};

/**
 * Extension definition.
 */
const extension: IRenderMime.IExtension = {
  id: 'jupyterlab-wav:plugin',
  rendererFactory,
  rank: 0,
  dataType: 'string',
  fileTypes: [
    {
      name: 'wav',
      fileFormat: 'base64',  // 追加
      mimeTypes: [MIME_TYPE],
      extensions: ['.wav']
    }
  ],
  documentWidgetFactoryOptions: {
    name: 'JupyterLab wav viewer',
    primaryFileType: 'wav',
    modelName: 'base64',  // 追加
    fileTypes: ['wav'],
    defaultFor: ['wav']
  }
};

export default extension;

上記コードを再度ビルドしてjupyterlabを再読み込みすれば完了です。

実際の表示の例

左のファイルブラウザからwavファイルをダブルクリックすると下記のようにaudio要素が表示されます。

audio要素

ちなみにwavファイルを右クリックして表示される「Open in New Browser tab」を実行するとこのExtensionがなくてもブラウザの別タブで再生は元々可能です。

npmjsへの投稿

Extension Developer GUidShipping Packagesの項を読むと extensionは単一のJavascriptパッケージであり、npmjs.orgから配布できることや、 jupyterlab-extensionというキーワードがpackage.jsonに含まれている場合JupyterLabのextension managerが見つけ出すことができるとの記載があります。 このキーワードはcookiecutterで自動生成された場合既に含まれているため、自ら追加する必要はありません。

よってnpmjs.orgへと登録することを考えます。 まずコマンドラインからnpmjsにログインしておきます。

$ npm adduser  # ユーザをnpmjs上に作成していない場合
$ npm login

次にpackage.jsonを編集し、nameの値をjupyterlab-wavから@wrist/jupyterlab-wavというように@username/extension-nameへと変えます。 更に必要に応じてhomepagelicenserepositoryなどのフィールドを追加します。

最後に、README.mdを編集してjupyter labextension installの部分を@wrist/jupyterlab-wavのように変更します。

上記を終えたら下記でnpmjsへとアップロードして終了です。

$ npm publish --access=public

無事にhttps://www.npmjs.com/package/@wrist/jupyterlab-wavへとアップロードされていれば、以後

$ jupyter labextension install @wrist/jupyterlab-wav

と打つことでインストールできるようになります。 実際に新規にコンテナを生成し直して上記を試したところ使えるようになりました。

今後

  • 他のMIMEタイプにも対応させる
    • 人によってはmp3も同様に開きたいこともあるかもしれませんが、MIME_TYPEの部分が配列になっていることから複数拡張子に対して同じような実装を使いませせるような気がしています
  • Web Audio APIを用いて波形描画やスペクトログラムを描画する

コメント

Comments powered by Disqus