Node.jsとytdl-coreでYouTubeをDLする時につまずいた点（DLできない、無音になる）

# はじめに
YouTube動画をDLしようとWebで検索すると、怪しげな広告が表示される海外のサービスばかりだったりします。こういったサービスを利用するのはちょっと怖いなと思うので、YouTube動画をDLできるアプリが欲しいと思い調べてみました。

調べてみたところ、[wintyoさん](https://qiita.com/wintyo)が [youtube-downloader](https://github.com/wintyo/youtube-downloader) というアプリを公開していたため、動かしてみることにしました。（[wintyoさん](https://qiita.com/wintyo)ありがとうございます）
その際に、動画ダウンロードの処理でつまずいた点があったため記事にしてみようと思います。

動かすにあたって以下の記事を参考にさせて頂きました。
 - [YouTube動画をダウンロードするWebアプリを作る](https://qiita.com/wintyo/items/4dd93221ae4094abd80a)
 - [Node.jsでYoutube動画をDLして保存するytdl-coreを使ったみたメモ](https://qiita.com/n0bisuke/items/af20c95a3dc6c22e5792)
# つまずいた点
## ダウンロードできない動画がある
アプリを立ち上げてダウンロードを実行してみると、動画によってダウンロードできるものとできないものがありました。いくつか試してみたところ、360pや480pで投稿されている動画はダウンロードできないことがわかりました。

(推察ですが)以下のようにytdlの引数に`quality: 'highest'`を指定した場合フルHDで動画をダウンロードするようリクエストしますが、YouTube側にフルHDでエンコードされた動画が用意されていないためエラーとなりダウンロードできないように見えました。

```
  const url = `https://www.youtube.com/watch?v=${youtubeId}`;
  const stream = ytdl(url, { quality: 'highest' });
```

以下のようにフォーマットの絞り込み`filter: format => format.container === 'mp4'`を追記すると、YouTube側に存在するエンコード形式の中から最高品質のエンコードを選ぶようになり、360pや480pの動画でもダウンロードできるようになりました。
```
  const url = `https://www.youtube.com/watch?v=${youtubeId}`;
  const stream = ytdl(url, { filter: format => format.container === 'mp4', quality: 'highestvideo' });
```

## 音声が無音になる
ダウンロードした動画を再生してみると、動画が映像のみで無音状態となっていました。

以下のように`filter: (format) => format.container === 'mp4'`のみを指定すると、音声付きでダウンロードできました。
```
  const url = `https://www.youtube.com/watch?v=${youtubeId}`;
  const video = ytdl(url,{filter: (format) => format.container === 'mp4' });
```
しかし、この設定ではダウンロードした動画は360p固定でした。

360p固定の理由は、[node-ytdl-core](https://github.com/fent/node-ytdl-core)のREADMEに以下記載があります。
> format 18 at 360p will be chosen first since it's the highest quality format with both video and audio. If you'd like a higher quality format with both video and audio, see the section on handling separate streams.

ビデオとオーディオの両方で最高品質のフォーマットのため、360pがデフォルトで選ばれる、というようなことが書かれています。（英語苦手なので間違えていたらご指摘ください）


## フルHDかつ高音質でDLしたい
上記に記載した通り、フルHDで動画をダウンロードしようとすると音声が無音になり、音声付きでダウンロードしようとすると動画が360p固定となります。

フルHDの動画でも音声をつけるための説明が[node-ytdl-core](https://github.com/fent/node-ytdl-core)のREADMEに書いてありました。

> Typically 1080p or better videos do not have audio encoded with it. The audio must be downloaded separately and merged via an encoding library. ffmpeg is the most widely used tool, with many Node.js modules available. Use the format objects returned from ytdl.getInfo to download specific streams to combine to fit your needs. Look at example/ffmpeg.js for an example on doing this.

1080p以上の動画にはオーディオが入っていないため、フルHDかつ音声付きの動画をダウンロードするには、動画と音声を個別にそれぞれダウンロードし、ffmpegでマージする必要があるようです。

# 実装
以下の通り、動画と音声を個別にそれぞれダウンロードし、ffmpegでマージするよう実装しました。
```js
  const destFilePath = path.resolve(__dirname, `./tmp/${youtubeId}`);
  const url = `https://www.youtube.com/watch?v=${youtubeId}`;

  // 略

  // 動画と音声をそれぞれダウンロード
  const video = ytdl(url, { filter: format => format.container === 'mp4', quality: 'highestvideo' })
  video.pipe(fs.createWriteStream(destFilePath + `.mp4`));

  const audio = ytdl(url, { quality: 'highestaudio' })
  audio.pipe(fs.createWriteStream(destFilePath + `.wav`));

  // 略

  const mergePath = destFilePath + `_merged.mp4`;

  // ffmpegでマージする
  exec(`ffmpeg -y -i ${destFilePath}.mp4 -i ${destFilePath}.wav -c:v copy -c:a aac -map 0:v:0 -map 1:a:0 ${mergePath}`, (err, stdout, stderr) => {
    // 中略
    res.download(mergePath);
  });
```

# 終わりに
以上がytdl-coreでYouTubeをDLする時につまずいた点でした。
フルHDかつ音声付きでダウンロードできるようになりました！やったー！
ytdlの仕様についてまだまだ理解が追いついていない部分が多いため、間違い等ありましたらご指摘お願いします🙏

今回作成したソースは以下です。興味がある方は見てください。

 - [リポジトリ](https://github.com/shoshoi/youtube-downloader)