R Markdownで音声つきスライド動画を自動生成

これは[R Advent Calendar 2017](https://qiita.com/advent-calendar/2017/rlang)の17日の記事です。

なお、この内容は先日行われた[Hijiyama.R final](https://atnd.org/events/90406)にて[私が発表した内容](https://kazutan.github.io/slides/HijiyamaR_final_youtubeR.html#/)をWeb記事に向けて加筆修正したものです。後半から箇条書きになっているのはそのあたりの都合です。お察しください。

# はじめに

## R Markdownとは

Rで様々な形式のドキュメントを生成するパッケージであり、関連する諸々を含めたシステムともいえます。詳細は省略します。

## R Markdownでスライド生成

R MarkdownはMarkdownをベースにドキュメントを生成し、markdownはPandocを利用してレンダリングします。Pandocにはhtmlスライドフォーマットへの出力にも対応しており、R Markdownはこれを利用したりしなかったりしてスライドを生成することができます

## スライド公開の問題点

htmlスライドのメリットは、そのファイルをWeb側にぽんと投げるだけで公開できることです。既存のスライド公開サービスに頼ることなく、また実際にプレゼンするにもブラウザで完結することから、私はすごく重宝しています。

ただ、これは他のスライド公開サービスも同じなのですが、スライドはあくまでスライド情報のみなので**音声は含まれません**。やはりトークは大事です。でも、「音声つきのスライド」となると、それはもはや動画と考えたほうがいいでしょう。そうなると、アテレコしてそれを録音・録画ということになるのですが、それは非常に作成側のコストがかかります。

今日はこの問題について、ariパッケージで解決を試みます。

## 要件の整理

- R上で音声つきスライド(動画)を生成したい
    - できればRmdで作ったスライドを活用したい
    - ムービーだから音声必須
    - いちいち録画/録音したくない

# ariパッケージ

## ariパッケージとは

- Rmdから生成したhtmlスライドからmp4ムービーを生成
    - 主に以下のRmdスライドに対応
        - ioslides_presentation
        - slidy_presentation
        - xaringan::moon_reader
    - 読み上げ原稿はRmdに仕込める
        - 音声は自動生成
    - 原稿にあわせてスライドも自動で切り替わる
        - タイミングも全自動で抽出

超簡単!

## 生成は`ari::narrate()`

```
ari_narrate(script, slides, output = "output.mp4", voice,
  capture_method = "vectorized", ...)
```

- script: 読み上げる原稿があるファイル
    - 詳細は後述
- slides: htmlスライド
    - Rmd直接ではなく，renderした後のhtmlスライドファイル
- output: 出力ファイル名
- voice: 声の選択(後述)
- capture_method: スライドをキャプチャする方法
    - 詳細は後述

## 実行例

`kosaki.Rmd`というioslidesで準備したファイル:

```
    ---
    title: "More Kosaki!"
    output: ioslides_presentation
    ---
    
    <!-- タイトルスライド用原稿 -->
    
    ## more! more kosaki.
    `r ''````{r cars, echo = TRUE}
    summary(cars)
    `r ''````
    <!-- 二枚目の原稿をひたすら綴る。-->
    ## kosaki! kosaki!
    `r ''````{r pressure}
    plot(pressure)
    `r ''````
    <!-- 三枚目の原稿を綴る -->
```


---

mp4を生成するコードは以下の通り:

```{r, eval=FALSE}
# まずはrender
rmarkdown::render("kosaki.Rmd", output_file = "kosaki.html")

# mp4を生成するコード
ari::ari_narrate("kosaki.Rmd", slides = "kosaki.html", output = "kosaki.mp4",
                 voice = "Mizuki", capture_method = "iterative")

```

- ところが，**何も準備しないとエラーが出て生成されない**
- 実はariパッケージはいろんなものを使って生成してる
    - というわけで，仕組みと環境構築を解説

# ariパッケージの仕組みと環境構築

## ariパッケージの処理フロー

1. `slides = `のhtmlスライド一枚一枚をwebshotで画像にする
1. `script = `内容から，各スライドの読み上げ原稿を抽出
1. 読み上げ原稿をAWS Pollyというサービスに投げて音声ファイルを取得
1. 音声ファイルの長さから，各スライドの表示時間を計算
1. 画像・音声と表示時間を元にffmpegで動画生成

## ariパッケージに必要な環境

- PhantomJS
    - webshotパッケージが利用しているヘッドレスブラウザ
- AWS PollyのAPIが使える環境
    - AWSアカウント
    - AWS Pollyが使えるトークン
- ffmpeg
    - Pathが通っている必要あり

…道は険しい。

## PhantomJSの導入

1. Rでwebshotパッケージをインストール
2. `webshot::install_phantomjs()`でOK
    - 各OSに応じて自動的にインストールされる
    - うまくインストールできない場合は，手動でインストールしてください

- webshotパッケージについては，以下のページを参照してください:
    - webshotパッケージ - Kazutan.R
    - https://kazutan.github.io/kazutanR/webshot_demo.html

## AWS Pollyが使える環境

- AWSのサービスのひとつ
    - テキストから音声データを生成
    - 詳しくはググってください
- ariパッケージはaws.pollyパッケージを利用
    - Cloudyrプロジェクトの一環
    - 興味がある方は「aws.polly r」でググッて
- 日本語にも対応
    - `voice`で読み手を指定できる
    - 日本の女性は`voice = "Mizuki"`

---

やることは以下の通り:

- AWSのアカウント取得
- AWS Pollyが使えるトークンを発行(説明省略)
- トークンの内容をRの設定として登録:

```{r, eval=FALSE}
# 上2つは架空の文字列です
Sys.setenv("AWS_ACCESS_KEY_ID" = "KOSAKIKOSAKIKOSAKIKO",
           "AWS_SECRET_ACCESS_KEY" = "KosakiKosakiKosakiKosakiKosakiKosakiKosa",
           "AWS_DEFAULT_REGION" = "ap-northeast-1")
```

- これらを設定しとけばOK
    - ただ，これら**トークン情報をRConsoleに直接入力するのは避けよう**

## ffmpegの準備

- コマンドラインから各種フォーマットのメディアを生成するアプリケーション
    - オープンソースでwindows, mac, 主要linuxディストリで利用可能
- 以下の公式サイトからダウンロード
    - https://www.ffmpeg.org/download.html
- Debian, UbuntuならaptでもOK
    - ただし，対応codecが含まれていないとだめ
    - 最終的にbuildしなきゃならん場合も...

## これで準備は整った

ariパッケージのインストール

```{r, eval=FALSE}
# cranから
install.packages("ari")

# githubはこちら(2017/11/24時点ではcranと同じ)
devtools::install_github("seankross/ari")
```

`ari::narrate()`を実行

```{r, eval=FALSE}
# mp4を生成するコード
ari::ari_narrate("kosaki.Rmd", slides = "kosaki.html", output = "kosaki.mp4",
                 voice = "Mizuki", capture_method = "iterative")
```

これでOK!

![ari_test_capture.png](https://qiita-image-store.s3.amazonaws.com/0/35118/10b27658-8633-492a-7998-f297316bb750.png)

# 改善と補足

## 環境準備が大変

- いろいろ事前の準備が大変
    - Rのパッケージ外でもいろいろ
    - 設定とか面倒
- アプリケーションをあまり追加したくない
    - 都合上あまり汚したくない
    - そもそもアプリをインストールできないことも

## docker、準備しました

- kazutan/zousan-r:tube_box
    - rocker/tidyverseをベース
        - tidyverseパッケージ全部入り
        - Debianベース
    - Rの日本語化済、IPAexフォント導入済
    - ariパッケージまで導入済
- AWSのトークンさえあればOK

```
$ docker pull kazutan/zousan-r:tube_box
$ docker run -p 8787:8787 -v ~:/home/rstudio -d --name kosaki kazutan/zousan-r:tube_box
```
## 補足 読み上げ原稿について

- `script`にmdファイルを与えた場合、renderして生成されたhtmlファイルのpタグひとつ分をスライド1枚分の原稿として処理
    - 見出しは原稿から無視
- `script`にRmdファイルを与えた場合、htmlコメント(`<!-- 文字列 -->`)一つ分をスライド1枚分の原稿として処理
    - htmlコメント以外は原稿から無視

状況や好みに応じて使い分ければ便利

## 補足 画像解像度について

- `ari::narrate()`の`...`はwebshotの引数として引き渡される
    - 解像度とかヘッドレスブラウザへ情報を送ることも可能
- ただし、解像度以外はいじらないほうがいい
    - htmlスライドはそれぞれ独自のエンジンで実現
    - 細かくやりたい場合はそれぞれの仕様をよく確認すること

## 補足 AWS Pollyの料金について

- 契約1年目は200万文字/月まで無料
- それを超えてもごくわずか
- 多分1年超えてもそれほどの金額ではない
- よほどのものを作り続けない限り心配なし!

## 補足 音声のみを生成したい

- ariパッケージではできない
- でもAWS.Pollyパッケージならいけるはず
    - このパッケージはまさに読み上げデータを取得するもの
    - こちらについては、各自で確認をおねがいします


**Enjoy!**