📽️

動画をトリミングしたい!! frontend編

2024/03/18に公開

動画トリミングについて

近年Youtubeなどの動画を見て過ごすことが多くなりつつある今日この頃。動画ってどうやってwebに表示して、どうやって編集することができるのだろうと思い、今回はトリミングをフロントエンド側でチャレンジしてみることにしました。

挑戦した経緯ごとに備忘録としてここに記していきたいと思います。

frontendでトリミングを試みる

【技術スタック】

  • "vite": "^5.0.11"
  • "vue": "^3.4.15"
  • "typescript": "~5.3.0"
  • "@ffmpeg/ffmpeg": "^0.12.10"
  • "@ffmpeg/util": "^0.12.1"

ffmpegがvideoを編集する際に便利だなと感じましたので、今回採用しております。

準備

まずはプロジェクトを準備します。

pnpm create vite

またffmpegをinstallします。

pnpm i @ffmpeg/ffmpeg @ffmpeg/util @types/ffmpeg --save

実装

今回は実際に処理を行っているところをピックアップしていきます。
今回は指定秒数〜指定秒数までをトリミングするというものを簡単に実装します。

今回メインとなるトリミングをする関数です。

async function trimVideo(inputFile: string, startTime: string, endTime: string) {
  const ffmpeg = new FFmpeg()
  const baseURL = 'https://unpkg.com/@ffmpeg/core-mt@0.12.6/dist/esm'

  await ffmpeg.load({
    coreURL: await toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
    wasmURL: await toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm'),
    workerURL: await toBlobURL(`${baseURL}/ffmpeg-core.worker.js`, 'text/javascript')
  })

  console.warn('ffmpeg loaded')

  // Write the file to use in FFmpeg
  await ffmpeg.writeFile('input.mp4', await fetchFile(inputFile))

  console.warn('input.mp4 written')

  // Run FFmpeg command to trim the video
  // too long to wait
  await ffmpeg.exec(['-i', 'input.mp4', '-ss', startTime, '-t', endTime, 'output.mp4'])

  console.warn('output.mp4 written')

  // Read the result
  const data = await ffmpeg.readFile('output.mp4')
  console.log('data', data)

  console.warn('output.mp4 read')

  // Create a URL for the output file to be used in the browser
  const url = URL.createObjectURL(new Blob([(data as Uint8Array).buffer], { type: 'video/mp4' }))

  console.warn('URL created')

  return url
}

この処理では指定したファイル(inputFile)を切り取って、output.mp4というファイル名でreturnするものとなっています。

使い方は下記のような感じです。

<script setup lang="ts">
function formatTime(seconds: number) {
  // calculate hours, minutes, seconds
  const hours = Math.floor(seconds / 3600)
  const minutes = Math.floor((seconds % 3600) / 60)
  const remainingSeconds = seconds % 60

  // format hours, minutes, seconds
  const formattedHours = hours < 10 ? '0' + hours : hours
  const formattedMinutes = minutes < 10 ? '0' + minutes : minutes
  const formattedSeconds = remainingSeconds < 10 ? '0' + remainingSeconds : remainingSeconds

  // return like '00:00:00'
  return formattedHours + ':' + formattedMinutes + ':' + formattedSeconds
}

const handleVideoExport = () => {
  store.waitingForFormatVideoFlag = true

  trimVideo(
    '/src/assets/sample.mp4',
    formatTime(Math.floor(10)),
    formatTime(Math.floor(20))
  ).then((trimmedVideoUrl) => {
    const downloadLink = document.createElement('a')
    downloadLink.download = 'video.mp4'

    downloadLink.href = trimmedVideoUrl

    downloadLink.setAttribute('hidden', 'true')
    document.body.appendChild(downloadLink)
    downloadLink.click()
    downloadLink.remove()

    store.waitingForFormatVideoFlag = false
  })
}
</script>

<template>
  <button
    @click="handleVideoExport"
  >
</template>

この処理ではボタンをクリックした時にhandleVideoExportが実行されます。

第一引数では'/src/assets/sample.mp4'という固定されたパスを指定し、'/src/assets/sample.mp4'にあるvideoを対象としてトリミングをしています。
第二引数ではformatTime(Math.floor(10))としていますが、これで10→00:00:10にフォーマットしてます。
第三引数も同様にformatTime(Math.floor(20))として20→00:00:20にフォーマットしてます。

trimVideoを実行するとtrimmedVideoUrlがreturnされ、それをダウンロードしていきます。
trimmedVideoUrlはhttp://localhost:5173/9066ade8-b368-4261-9403-1a7c10177f35というようなurlです。

動かしてみた!!

上で実装が完了したので、試しに20秒ほど動画をトリミングしてみました。
結果は成功!!
指定した秒数でトリミングされていました。

しかし、、、

重いし遅いです!!!笑

少し予想はしていましたが、すごく遅いです。やはりこういった大きいデータを扱うものはフロントエンドにはちょっと不向きなのかなと感じました。
もしフロントでやる場合は小さなファイルをuserにアップロードしていただくように、fileサイズなどでvalidationをかけるべきだなとも思いました。

今後は?

今後はこれをバックエンド(Node)で実装したいと思います。
色々調査が必要になってくるので、また実装でき次第記事を書こうかなと思います!

以上、簡単でしたがfrontendで動画をトリミングするロジックでした。

Discussion