📹

学校のタブレットでYoutubeが見れないのでダウンロードできるようにした話 後半

2022/05/11に公開

前回の記事で最後に挙げた問題点を解決する

replitのストレージが少ない(20本くらい動画をダウンロードすると埋まってしまう)。
②ダウンロードされるファイル名がIDなので分かりにくい。
③(建前は部活で音源を聞くことなので)オーディオ形式でダウンロードできるようにする。

ストレージの問題を解決する

これはもうどうしようもないので、起動時&定期的にtmpフォルダ内のデータを削除するしかない。どなたかいい案があったらコメントで教えてください。

定時実行にはnode-cronを使用した。

前回のコードに以下のコードを追加。

const cron = require("node-cron");


function clearFiles() {
  fs.readdir("./tmp", function(err, files) {//tmp以下にあるファイルを探す
    if (err) {
      throw err;
    }
    files.forEach(function(file) {//tmp以下の各ファイルに対して
      fs.unlink(`./tmp/${file}`, function(err) {//削除を実行
        if (err) {
          throw (err);
        }
      });
    });
  });
}
clearFiles();//起動時に実行
cron.schedule('*/30 * * * *', () => {//起動後30分おきに実行
  clearFiles();
});

因みにreplitは1日に数回、UptimerRobotの更新が間に合わずに落ちることがあるので、そのタイミングでファイルが消えることがある(replitはユーザーがリポジトリで作業していない状態で追加されたファイルや変更は再起動時に消える)。

ファイル名わけわからん

前回のコードだとファイル名がIDの状態でダウンロードされてしまう。

特にサウンドトラックを複数個ダウンロードするとサムネが同じなので聞いてみるまで中身がわからない。

そこでyoutubeのapiを使用して動画のタイトルを取得し、ダウンロードされるファイルの名前を変更する。

パッケージはsimple-youtube-api を使用した。APIキーの取得方法はこちらの記事などを参考に…

前回のコードの冒頭に以下のコードを追加

const YouTubeAPI = require('simple-youtube-api');
const youtube = new YouTubeAPI('ここにapiキー');

前回のコードを次のように書き換え(追加、変更点にはコメントをしてあります)。


app.get('/api/ytdl/:youtubeId', async function(req, res) {
  const { youtubeId } = req.params;
  const destFilePath = path.resolve(__dirname, `./tmp/${youtubeId}.mp4`);

  let info = await ytdl.getInfo(youtubeId);
  //apiを使用してタイトルを取得
  let data = await youtube.getVideo(info.videoDetails.video_url);

  if (fs.existsSync(destFilePath)) {
    //ダウンロードするファイル名をタイトルに変更
    res.download(destFilePath, data.title)
    return;
  }

  let stream = null;
  stream = ytdl(info.videoDetails.video_url, { filter: (format) => format.container === "mp4", highWaterMark: 32 * 1024 * 1024 })
    stream.pipe(fs.createWriteStream(destFilePath))

    stream.on('error', (err) => {
      console.error(err);
      res.status(400).send('download error!');
    });
    stream.on('end', () => {
      console.log(`${youtubeId}.mp4 downloaded.`);
      //ダウンロードするファイル名をタイトルに変更
      res.download(destFilePath, data.title);
      return;
    })
});

Youtubeからサーバーにダウンロードするファイル名がIDのままなのは、奇跡的にIDの違う同じタイトルの動画をダウンロードしていた場合に事故らないようにするため。

これで端末側にダウンロードするファイルの名前がタイトルになりました。

オーディオのみのダウンロードに対応する。

最初に述べた通りこのapiは部活で使用するために作ったものである。音源を聴く時に動画はいらないので使用可能容量が1.5GBしか残っていないタブレットのためにもオーディオのみにして軽量化を図りたい。

mp4→mp3の変換には以下のパッケージを使用した。

  • @ffmpeg-installer/ffmpeg
  • fluent-ffmpeg
    コードの冒頭に以下のコードを追加
const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;//ffmpegのインストール先
const ffmpeg = require('fluent-ffmpeg');
ffmpeg.setFfmpegPath(ffmpegPath);//fluent-ffmpegを使うためのパスを通す

先ほどのダウンロードするファイル名を変えたコードを以下のように書き換え(変更箇所にはコメントしてあります)

app.get('/api/ytdl/:youtubeId', async function(req, res) {
  const { youtubeId } = req.params;
  //ダウンロードするファイルのタイプを取得
  //https://リポジトリ名.ユーザー名.repl.co/api/ytdl/動画のID?fileType=mp3
  //のような形で入力する
  let fileType = req.query.fileType || 'mp4';//指定がなければmp4
  //mp3,mp4以外の文字が入力されていた場合、mp4にする
  if (fileType !== 'mp4') {
    if (fileType !== 'mp3') {
      fileType = 'mp4';
    }
  }
  //パスのファイルタイプを入力に合わせて変更
  const destFilePath = path.resolve(__dirname, `./tmp/${youtubeId}.${fileType}`);
  let info = await ytdl.getInfo(youtubeId);
  let data = await youtube.getVideo(info.videoDetails.video_url);
  if (fs.existsSync(destFilePath)) {
    res.download(destFilePath, data.title)
    return;
  }
  let stream = null;
  stream = ytdl(info.videoDetails.video_url, { filter: (format) => format.container === "mp4", highWaterMark: 32 * 1024 * 1024 })
//mp3の場合、ffmpegを使用してmp3のファイルをダウンロードする
  if (fileType === 'mp3') {
    ffmpeg(stream)
      .audioBitrate(128)
      .save(destFilePath)
      .on('error', (err) => {
        console.error(err);
        res.status(400).send('download error!');
      })
      .on('end', () => {
        console.log(`${youtubeId}.${fileType} downloaded.`);
        res.download(destFilePath, data.title);
        return;
      });
  //mp4の場合、今まで通り。
  } else {
    stream.pipe(fs.createWriteStream(destFilePath))

    stream.on('error', (err) => {
      console.error(err);
      res.status(400).send('download error!');
    });
    stream.on('end', () => {
      console.log(`${youtubeId}.${fileType} downloaded.`);
      res.download(destFilePath, data.title);

      return;
    })
  }
});

これでmp3がダウンロードできるようになりました!

容量が節約できます!やったね!

全体のコード

グダグダ書いたのでコードが途切れ途切れになってしまいました。

なので、GitHubにこれまでに書いたコードを置いておきます。
https://github.com/Aiueokashi/Youtube-Downloader-for-replit/

まとめ

前回の問題は解決できたのであとは見た目がダサい(というか見た目が無い)のを解決すればok。
フロントエンドはnetlifyとかで作ろうかなと

とりあえず満足したのでしばらくこれで行くつもりです。

前半↓
https://zenn.dev/aiueokashi/articles/527a4830d6c513

Discussion