Vue.js v-for template の v-bind:key

virtual dom の nodeには一意のキーが必要 componentはnamespaceを作るけど、templateは作らない。 なので、例えば一つの.vueファイルの中で、<template v-for=が2つあったらそのtemplateの子要素にはその.vueファイルでuniqueになるようにkeyを与えなきゃならない。v-bind:key="'list1-' + index" v-bind:key="'list2-" + indexみたいに。 templateが2つじゃなくても、templateの子が2つ以上だったりしても同じ。だと思う。

と理解した。

forum.vuejs.org

JavaScriptの時刻

moment.jsを使っとけばいいのだけれど、JavaScriptのDateがどういうものか調べた。

Dateオブジェクトは、UNIX epochからのミリ秒をデータとして持つ。 UNIX epoch = 1970 1/1 00:00:00 (UTC)

引数なしコンストラクタcallのときは現在時(UTC)。

ホストシステムのタイムゾーン = 現在のタイムゾーン Intl.DateTimeFormat().resolvedOptions().timeZone1

Date.parseは、ISO8601の書式以外ではブラウザの実装依存の結果になる。moment.jsを使っとけばいい。

memolog.org

JavaScript の Jupyter Notebook を Google Cloud Shell の Docker で 動かす

いつも通り、Google Cloud Shell での作業。 JavaScript で Firestore を使うのにあれこれ書いて試したかった。


Google Cloud Shell で Jupyter Notebook を Dockerコンテナとして動かす

Jupyter プロジェクト公式のイメージが Docker Hub に置いてある。

jupyter-docker-stacks.readthedocs.io

今回は、jupyter/base-notebook を使う。 ここの Dockerイメージは、jovyan1というユーザーで Jupyter が起動するようにセットアップしてある。

docker run -p 8080:8888 jupyter/base-notebook

Google Cloud Shell のプレビュー機能でアクセスできるけど、新しいノートブック作ったりできない。リクエストのホスト名チェックではじかれてしまう。

こんなエラーメッセージ Blocking Cross Origin API request for /api/contents.

これは Jupyter の起動オプション2で待ち受けホスト名を指定すればいい。あとついでに、token 認証も無効にする。Google Cloud Shell のプレビューだと認証済みのリクエストしか通ってこないから Jupyter の認証を無効にしてもよい。起動オプションを指定するときは、起動スクリプトも指定する必要がある。

docker run 
    -p 8080:8888 
    jupyter/base-notebook 
    start-notebook.sh --NotebookApp.allow_origin="*" --NotebookApp.token=""

jupyter/base-notebook からカスタムイメージを作る

同じことは Dockerfile でもできるが、今回はコンテナにアタッチして手動で設定した。

IJavascript をイントールする

IJavascript のコードリポジトリにある Dockerfile3 を参考にした。ついでに firebase の Web SDK も追加しておく。

export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/conda/bin"
npm install -g ijavascript firebase
ijsinstall

実際コードで require するときには node のグローバルモジュールのパスも追加する必要がある。それは後で Dockerイメージに ENV で書き込む。モジュールがインストールされるパスは npm root -g で表示できる。

Jupyter Notebook を設定する

Notebook をアップデートする

いま(2019/11/21)の latest イメージだと、Notebook の keyboard shortcut エディターが使えないなどの不具合がある。

サーバー側ではこんなエラーメッセージ 404 GET /static/components/react/react-dom.production.min.js

notebook のバージョンを最新では解決されてる。コンテナの中で conda コマンドを使ってアップデートする。

conda install notebook

Notebook の起動オプションを設定する

起動オプションを設定ファイルで設定する。/home/jovyan/.jupyter/juypter_notebook_config.pyに書く。

c.NotebookApp.allow_origin='*'
c.NotebookApp.token=''

Notebook のショートカットキーを設定する

Cell の編集モードで Shift-Enter を押したときに Cell が実行されるのを無効にしたい。チャットツールとかで改行が Shift-Enter だから反対の癖がつくとやっかいだから。それと、ctrl - [で編集モードから抜けるショートカットキーを追加したい。

Keyboard Shortcut Customization — Jupyter Notebook 6.0.2 documentation

/home/jovyan/.jupyter/custom/custom.jsに書く。書く内容は notebook の cell に マジックコマンド%%javascript を使うか、ブラウザの デベロッパーツールのコンソールで実行して確認できる。4

Cell の編集モードは CodeMirror でできている。Jupyter が管理するショートカットキーよりも CodeMirror の持つキー設定が勝つ。ctrl - [は、CodeMirror ではインデント解除に割り当てられているのでそれを削除する。

Jupyter.keyboard_manager.edit_shortcuts.remove_shortcut('Shift-Enter');
Jupyter.keyboard_manager.edit_shortcuts.add_shortcut("ctrl-[", "jupyter-notebook:enter-command-mode")
delete CodeMirror.keyMap.default["Ctrl-["]

編集した Dockerイメージを保存する

上でいじったコンテナをもとに Dockerイメージを作る。ついでに、環境変数と起動オプションを設定する。

docker commit 
    -c "ENV NODE_PATH /opt/conda/lib/node_modules" 
    -c 'CMD ["start-notebook.sh"]' 
    <ここにコンテナid>
    <ここに作成するイメージ名>

Docker Hub にアカウントがあってdocker loginコマンドでログインしてあれば docker pushで自分のイメージをDocker Hub に置ける。

Firestore local emulator に Firebase JavaScript SDK (Web) で接続する

admin SDK ではなくて、Webブラウザで使う firebase-js-sdk で firestore ローカルエミュレーターに接続する方法。

const db = firebase.firestore();
db.settings({
    host: "172.18.0.1:18080",
    ssl: false
});

Connect your app and start prototyping  |  Firebase

他の SDK では環境変数 FIRESTORE_EMULATOR_HOST で設定できるが、Web SDK環境変数を読まない。

Can't connect to local Firestore emulator · Issue #1721 · firebase/firebase-js-sdk · GitHub

Firestore local emulator に Docker コンテナから接続する

$ firebase setup:emulators:firestore

firebase.json

{
  "firestore": {}
  "emulators": {
    "firestore": {
      "host": "0.0.0.0",
      "port": "18080"
    }
  }
} 

"0.0.0.0"で全てのIPで待ち受けをして、リクエストのホスト名チェックも無効になる。 今回はgoogle cloud shell の中で実行する。認証なしでは外からアクセスできないから大丈夫。

Docker コンテナから Docker ホストへは設定なしでアクセス可能。ただし、コンテナから見たホストのIPアドレスが必要になる。

こんな感じでコンテナ作成時にIPアドレスを渡して置く

docker container run -e "DOCKER_HOST=$(ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+')" ...

Docker Tip #65: Get Your Docker Host's IP Address from in a Container — Nick Janetakis

$ curl "$DOCKER_HOST":8080 で "OK"が返ってきたら成功。

文字起こしと発声時刻で動画に文字を表示してみた

WSL の debian で作業した。 apt install ffmpegFFmpegをインストールした。

音声ファイルを静止画の動画にする

ffmpeg
  -loop 1
  -r 30
  -i image.png
  -i audio.mp3
  -pix_fmg yuv420p
  movie.mp4

文字起こしと文節ごとの発声時刻データを作る

とりあえず、Google の Cloud Speech-to-Text を使った。他の音声認識サービスでも同じようなものだろう。Cloud Speech-to-Text では単語ごとの発声時刻も取得できる。文字を表示させるのは単語単位よりも文節単位のほうがよさそうに思った。文字起こしした文章を GiNZA を使って文節に分けた。

文字起こしと単語とごの発声時刻

gcloud beta ml speech recognize
  audio.mp3
  --language-code="ja-JP"
  --encoding="mp3"
  --sample-rate=44100
  --include-word-time-offsets

サンプルレートが分からないときはffmpeg -i audio.mp3で確認できる。

文節に分ける

GiNZA のインストールを手元でやると時間がかかるから、Google Colaboratory を使った。

pip install "https://github.com/megagonlabs/ginza/releases/download/latest/ginza-latest.tar.gz"
import spacy
nlp = spacy.load('ja_ginza')
doc = nlp('まずは心を鎮めます。目を閉じて。全身の力をすーっと抜いていきます')
for sent in doc.sents:
    for token in sent:
        print(token.i, token._.bunsetu_index, token.orth_)
    print('EOS')

token._.bunsetu_indexで文節のインデックスが取得できる。

動画に文字を書き込む

今回は手っ取り早く FFMpeg の drawtext を使った。

日本語フォントをインストールする

IPA フォントにした。

sudo apt install fonts-ipafont

フォントのパスの確認方法

fc-list

文字を書き込む位置と場所とタイミングを決める

今回は適当に手で書いた。表示するフレームは発声時刻のデータを使う。

drawtext
    =text='まずは'
    :fontfile='/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf'
    :fontsize=60
    :fontcolor='#FFF100'
    :enable='between(n, trunc(0 * 30), trunc((43/10) * 30))'
    :y=(th * 1)
,drawtext
    =text='心を'
    :fontfile='/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf'
    :fontsize=60
    :fontcolor='#FFF100'
    :enable='between(n, trunc((7/10) * 30), trunc((43/10) * 30))'
    :y=(th * 2) + 10
,drawtext
    =text='静めます'
    :fontfile='/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf'
    :fontsize=60
    :fontcolor='#FFF100'
    :enable='between(n, trunc((22/10) * 30), trunc((43/10) * 30))'
    :y=(th * 3) + 10


,drawtext
    =text='目を'
    :fontfile='/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf'
    :fontsize=60
    :fontcolor='#FFF100'
    :enable='between(n, trunc((43/10) * 30), trunc((83/10) * 30))'
    :y=(th * 1)
,drawtext
    =text='閉じて'
    :fontfile='/usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf'
    :fontsize=60
    :fontcolor='#FFF100'
    :enable='between(n, trunc((49/10) * 30), trunc((83/10) * 30))'
    :y=(th * 2) + 10

......

書き込む

ffmpeg -i movie.mp4 -vf "`cat draw.txt`" result.mp4

その他

字幕を SVG アニメーションで作ったら Web の編集ツールとか工夫できそうで面白そう。

音の波形で文字の装飾とかできそう。

音声を字幕動画にするのに、たぶん一番面倒そうな発声タイミングと文字表示の同期が簡単にできることがわかった。