Node.jsでYoutube動画をDLして保存するytdl-coreを使ったみたメモ


Node.jsでYoutubeの動画ファイルをごにょごにょって話で調べるとけっこう色々なライブラリが出てきます。

> https://www.npmjs.com/search?q=youtube-dl

今回は、更新が比較的新しいのでメンテナンスされていることに期待出来そうな、`ytdl-core`というモジュールを使ってみます。使ったみた雰囲気だとffmpegを利用しないでもある程度利用できそうでした。

> https://github.com/fent/node-ytdl-core

ちなみに[コマンドラインツール版のytdl](https://www.npmjs.com/package/ytdl)もあります。

あと、[自分の動画](https://www.youtube.com/watch?v=9CHiwJhx3qw)で試してます。 

## 環境

* Node.js 10.11.0
* npm 6.4.1
* macOS Mojave 10.4

## インストール

```bash
npm i ytdl-core
```

## DLのみ

```js
const fs = require('fs');
const ytdl = require('ytdl-core');
const BASE_PATH = `https://www.youtube.com/watch?v=`;

const youtubeId = `9CHiwJhx3qw`; //DLするYoutube動画のID（urlのv=の後ろの部分11桁）
const url = BASE_PATH+youtubeId;

ytdl(url).pipe(fs.createWriteStream(`${youtubeId}.mp4`));
```

## プログレス表示と動画の情報取得

DLが完了した際のイベントを取得したい時は以下のように`'end'`のイベントでハンドリングできます。

> こちらのサンプルコード二つを参考にしています。 
> https://github.com/fent/node-ytdl-core/blob/master/example/progress.js
> https://github.com/fent/node-ytdl-core/blob/master/example/get_info.js

今回は`readline`も表示用に使います。

```bash
npm i readline
```

```app.js
const fs = require('fs');

const ytdl = require('ytdl-core');
const readline = require('readline'); //表示用途

const BASE_PATH = `https://www.youtube.com/watch?v=`;

const youtubeId = `9CHiwJhx3qw`; //DLするYoutube動画のID（urlのv=の後ろの部分11桁）
const url = BASE_PATH+youtubeId;

const video = ytdl(url,{filter: (format) => format.container === 'mp4' });

let starttime;

video.pipe(fs.createWriteStream(`${youtubeId}.mp4`));

video.once('response', () => starttime = Date.now());

video.on('progress', (chunkLength, downloaded, total) => {
    const floatDownloaded = downloaded / total;
    const downloadedMinutes = (Date.now() - starttime) / 1000 / 60;
    readline.cursorTo(process.stdout, 0);
    process.stdout.write(`${(floatDownloaded * 100).toFixed(2)}% downloaded`);
    process.stdout.write(`(${(downloaded / 1024 / 1024).toFixed(2)}MB of ${(total / 1024 / 1024).toFixed(2)}MB)\n`);
    process.stdout.write(`running for: ${downloadedMinutes.toFixed(2)}minutes`);
    process.stdout.write(`, estimated time left: ${(downloadedMinutes / floatDownloaded - downloadedMinutes).toFixed(2)}minutes `);
    readline.moveCursor(process.stdout, 0, -1);
});

video.on('end', () => {
    process.stdout.write('\n\n');
    
    //DLしたYoutube動画の情報
    ytdl.getInfo(youtubeId, (err, info) => {
        if (err) throw err;

        console.log('\n動画情報もろもろ');
        console.log(info.player_response.videoDetails);

        console.log('\n動画タイトル');
        console.log(info.player_response.videoDetails.title);

        console.log('\n秒数');
        console.log(info.player_response.videoDetails.lengthSeconds);
        
        console.log('\nサムネイル');
        console.log(info.player_response.videoDetails.thumbnail);
    });
});
```

結果はこんな感じ

```bash
$ node app.js

動画情報もろもろ
{ videoId: '9CHiwJhx3qw',
  title: '【検証】うんこボタンであのイタズラは成功するか！？【予想外の展開】',
  lengthSeconds: '288',
  keywords: [ 'iot', 'iotlt', 'うんこボタン', 'ドッキリ', 'イタズラ', '電子工作' ],
  channelId: 'UC42TOX3Ff7H0iORoPoBkAcg',
  isCrawlable: true,
  thumbnail: { thumbnails: [ [Object], [Object], [Object], [Object] ] },
  viewCount: '298',
  author: 'どっとすたぢお',
  isLiveContent: false }

動画タイトル
【検証】うんこボタンであのイタズラは成功するか！？【予想外の展開】

秒数
288

サムネイル
{ thumbnails:
   [ { url:
        'https://i.ytimg.com/vi/9CHiwJhx3qw/hqdefault.jpg?sqp=-oaymwEiCKgBEF5IWvKriqkDFQgBFQAAAAAYASUAAMhCPQCAokN4AQ==&rs=AOn4CLAS-n8-TmsSRbr_gbKFLAuBY22Rkg',
       width: 168,
       height: 94 },
     { url:
        'https://i.ytimg.com/vi/9CHiwJhx3qw/hqdefault.jpg?sqp=-oaymwEiCMQBEG5IWvKriqkDFQgBFQAAAAAYASUAAMhCPQCAokN4AQ==&rs=AOn4CLA0WQFxu5lN1nHd3S4qYabeCvioKA',
       width: 196,
       height: 110 },
     { url:
        'https://i.ytimg.com/vi/9CHiwJhx3qw/hqdefault.jpg?sqp=-oaymwEjCPYBEIoBSFryq4qpAxUIARUAAAAAGAElAADIQj0AgKJDeAE=&rs=AOn4CLA3j-N0ahl91bywiKcv7vGOiD_3Pw',
       width: 246,
       height: 138 },
     { url:
        'https://i.ytimg.com/vi/9CHiwJhx3qw/hqdefault.jpg?sqp=-oaymwEjCNACELwBSFryq4qpAxUIARUAAAAAGAElAADIQj0AgKJDeAE=&rs=AOn4CLBL4Tit-bNE3WbNMitKo2TdGrMu-A',
       width: 336,
       height: 188 } ] }
```

サムネイルが取れるの面白いですよね。

> [![](https://i.ytimg.com/vi/9CHiwJhx3qw/hqdefault.jpg?sqp=-oaymwEjCNACELwBSFryq4qpAxUIARUAAAAAGAElAADIQj0AgKJDeAE=&rs=AOn4CLBL4Tit-bNE3WbNMitKo2TdGrMu-A)](https://www.youtube.com/watch?v=9CHiwJhx3qw)

## その他

ほんとはmp4じゃなくて音源だけのmp3を撮りたかったけどffmpegが必要っぽいとのことと、[README](https://github.com/fent/node-ytdl-core)を見る限り`highestaudio`や`audioonly`なども指定できるっぽいけど指定してDLされたファイルが壊れてしまう現象になった（謎）のでとりあえずmp4で試してみました。

バイナリが壊れる現象はオプションの指定の仕方が良くないような気がするので時間があれば再チャレンジしてみたい気持ち