最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript實(shí)現(xiàn)視頻轉(zhuǎn)GIF的示例代碼

 更新時(shí)間:2024年03月06日 08:37:06   作者:可樂(lè)雞翅kele  
這篇文章主要介紹了JavaScript實(shí)現(xiàn)視頻轉(zhuǎn)GIF,本文一共會(huì)按照以下三步去實(shí)現(xiàn)一個(gè)視頻轉(zhuǎn)?GIF?功能,解封裝視頻,從視頻文件中獲取視頻幀,解碼視頻幀,獲取幀圖像信息,拼裝幀圖像信息,生成?GIF,需要的朋友可以參考下

前言

之前使用過(guò)FFMpeg來(lái)做視頻轉(zhuǎn)GIF,但是FFMpeg的體積還是太大了,前端加載一般要10M左右。后面發(fā)現(xiàn)了 Webcodecs 這個(gè)新的 Web API ,它提供了解碼視頻的能力。所以就沿著這個(gè)方向去使勁,也是實(shí)現(xiàn)了一個(gè)純前端的在線(xiàn)的視頻轉(zhuǎn) GIF 功能。

本文一共會(huì)按照以下三步去實(shí)現(xiàn)一個(gè)視頻轉(zhuǎn) GIF 功能:

  • 解封裝視頻,從視頻文件中獲取視頻幀
  • 解碼視頻幀,獲取幀圖像信息
  • 拼裝幀圖像信息,生成 GIF

視頻解封裝

視頻解封裝是從一個(gè)包含多種媒體數(shù)據(jù)的容器中提取出特定類(lèi)型的媒體數(shù)據(jù)的過(guò)程。通過(guò)解封裝,可以從容器中分離出視頻軌道、音頻軌道等各種媒體數(shù)據(jù)。

它的主要目的是獲取原始的音頻、視頻等媒體數(shù)據(jù),以便進(jìn)行后續(xù)的處理,比如播放、編輯或者轉(zhuǎn)碼。解封裝后的數(shù)據(jù)可以根據(jù)需要被送入相應(yīng)的解碼器進(jìn)行解碼。

這里使用到的是 mp4box.js 這個(gè)庫(kù)去解碼上傳的視頻文件,以獲取視頻軌道信息。首先定義一個(gè)獲取文件 Buffer 的方法,我這里是上傳文件然后去獲取 ArrayBuffer

const getFileArrayBuffer = (file) => {
  return new Promise((resolve) => {
    const reader = new FileReader();
    reader.onload = (e) => {
      resolve(e.target.result);
    };
    reader.readAsArrayBuffer(file);
  });
};

然后調(diào)用 mp4box 去解封裝視頻的軌道信息

const data = await getFileArrayBuffer(file);

data.fileStart = 0;

const getVideoInfo = async (data) => {
  return new Promise((resolve, rejcet) => {
    const mp4boxfile = MP4Box.createFile();
    mp4boxfile.onError = function (e) {
      console.log("e", e);
      rejcet(e);
    };
    mp4boxfile.onReady = (info) => {
      resolve({
        mp4boxfile,
        info,
      });
    };
    mp4boxfile.appendBuffer(data);
    mp4boxfile.flush();
  });
};

const { mp4boxfile, info } = await getVideoInfo(data);
console.log(info);
const videoTrack = info.tracks.find((track) => track.type === "video");
const timescale = videoTrack.timescale;
const duration = videoTrack.duration / timescale;
const nbSamples = videoTrack.nb_samples;
const fps = Math.round(nbSamples / duration);

以下大概是一個(gè)視頻軌道的字段:

這里如果我們想獲取視頻的時(shí)長(zhǎng),幀率等信息,需要做一些小小的轉(zhuǎn)換。nb_samples是視頻總幀數(shù); movie_timescale 我理解是視頻的一個(gè)采樣單位,拿 movie_duration/movie_timescale 才是我們視頻的長(zhǎng)度,這里大概是 18.2 秒。幀率就是總幀數(shù)/視頻時(shí)長(zhǎng),這里大概是 15FPS 。

獲取視頻幀

獲取視頻幀這里用到的是一個(gè)較新的 Web API , VideoDecoderEncodedVideoChunk ,它們的API兼容性如下:

  • VideoDecoder是一個(gè)較新的API,它可以讓我們通過(guò)JS在瀏覽器中解碼視頻
  • EncodedVideoChunk是指表示視頻編碼數(shù)據(jù)塊對(duì)象,用于表示已經(jīng)編碼的視頻數(shù)據(jù),這些數(shù)據(jù)可以通過(guò)網(wǎng)絡(luò)傳輸并在接收端進(jìn)行解碼。

我們利用VideoDecodermp4box解封裝后得到的軌道信息進(jìn)一步解析成一幀一幀的圖片,為我們后續(xù)的合成GIF做準(zhǔn)備。

const videoFrames = [];
const initDecoder = () => {
  const getExtradata = () => {
    // 生成VideoDecoder.configure需要的description信息
    const entry = mp4boxfile.moov.traks[0].mdia.minf.stbl.stsd.entries[0];

    const box = entry.avcC ?? entry.hvcC ?? entry.vpcC;
    if (box != null) {
      const stream = new MP4Box.DataStream(
        undefined,
        0,
        MP4Box.DataStream.BIG_ENDIAN
      );
      box.write(stream);
      // slice()方法的作用是移除moov box的header信息
      return new Uint8Array(stream.buffer.slice(8));
    }
  };

  // 初始化 VideoDecoder
  const decoder = new VideoDecoder({
    output: (videoFrame) => {
      createImageBitmap(videoFrame).then((img) => {
        videoFrames.push({
          img,
          duration: videoFrame.duration,
          timestamp: videoFrame.timestamp,
        });
        videoFrame.close();
        if (videoFrames.length === nbSamples) {
          const canvas = document.getElementById("canvas");
          const ctx = canvas.getContext("2d");
          const img = videoFrames[0].img;
          console.log(img);
          ctx.drawImage(img, 0, 0, img.width, img.height);
        }
      });
    },
    error: (err) => {
      console.error("videoDecoder錯(cuò)誤:", err);
    },
  });
  const config = {
    codec: videoTrack.codec,
    codedWidth: videoTrack.video.width,
    codedHeight: videoTrack.video.height,
    description: getExtradata(),
  };
  decoder.configure(config);
  return decoder;
};
let decoder = initDecoder();

const getChunkList = () => {
  const track = mp4boxfile.getTrackById(videoTrack.id);
  console.log(track.samples.length);
  const chunkList = track.samples.map((_, index) => {
    const sample = mp4boxfile.getSample(track, index);
    const type = sample.is_sync ? "key" : "delta";
    const chunk = new EncodedVideoChunk({
      type,
      timestamp: sample.cts,
      duration: sample.duration,
      data: sample.data,
    });
    return chunk;
  });
  return chunkList;
};
const chunkList = getChunkList();
chunkList.forEach((chunk) => decoder.decode(chunk));

大概解釋一下上面的代碼:

  • initDecoder中我們初始化了一個(gè)VideoDecoder,它接收到數(shù)據(jù)之后就會(huì)響應(yīng)output回調(diào),在output回調(diào)中我們把videoFrame轉(zhuǎn)成了一個(gè)ImageBitmap對(duì)象(即幀圖像信息),然后收集起來(lái)。
  • 然后我們實(shí)現(xiàn)了一個(gè)getChunkList函數(shù)來(lái)收集解封裝后的視頻數(shù)據(jù),把所有的chunk收集起來(lái)供decoder調(diào)用
  • 兩者配合起來(lái),我們就可以拿到這段視頻軌道的所有視頻幀圖像

合成GIF

當(dāng)所有的視頻幀處理完成之后,docoder會(huì)觸發(fā)一個(gè)flush方法,我們可以在這里進(jìn)行GIF的合成。這里我GIF合成使用的庫(kù)是gif.js。實(shí)現(xiàn)代碼如下:

decoder.flush().then(() => {
  const width = videoFrames[0].img.width;
  const height = videoFrames[0].img.height;
  var gif = new GIF({
    workers: 4,
    quality: 10,
    width,
    height,
  });

  console.log("開(kāi)始");
  videoFrames
    .map((frame) => frame.img)
    .forEach((imageBitmap) => {
      var offscreenCanvas = new OffscreenCanvas(
        imageBitmap.width,
        imageBitmap.height
      );
      var offscreenContext = offscreenCanvas.getContext("2d");
      offscreenContext.drawImage(imageBitmap, 0, 0);

      var imageData = offscreenContext.getImageData(
        0,
        0,
        imageBitmap.width,
        imageBitmap.height
      );
      gif.addFrame(imageData, { delay: 1000 / fps });
    });

  gif.on("finished", function (blob) {
    var link = document.createElement("a");
    link.href = URL.createObjectURL(blob);
    link.download = "animated.gif";
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  });

  // 開(kāi)始生成 GIF
  gif.render();
});

簡(jiǎn)單解釋一下上面的代碼:

  • 由于生成的imageBitmap并不能直接喂給gif.addFrame調(diào)用,所以這里使用了一個(gè)離屏Canvas去轉(zhuǎn)換一下
  • gif.addFrame(imageData, { delay: 1000 / fps });這里的delay參數(shù)就是每一幀圖片持續(xù)的時(shí)長(zhǎng),默認(rèn)是500ms,我們用1秒除于幀率,來(lái)?yè)Q算出實(shí)際的時(shí)長(zhǎng)
  • 合成完畢之后,通過(guò)一個(gè)a標(biāo)簽把GIF下載下來(lái)

通過(guò)這樣的方式,一個(gè)1M多MP4生成出來(lái)的GIF居然有30M,我滴媽呀。雖然質(zhì)量跟流暢度還是挺好的,但這個(gè)體積也太嚇人了。

所以我們最好對(duì)GIF進(jìn)行一個(gè)壓縮,這個(gè)場(chǎng)景下壓縮主要是減少合成GIF的幀圖像以及壓縮每一幀圖像的體積。

所以接下來(lái)我們會(huì)做如下操作:

  • new GIF的畫(huà)布寬高縮小一半
  • 逢兩幀抽取一幀,每一幀的延時(shí)變成原來(lái)的2
  • 對(duì)每一幀進(jìn)行壓縮

完整代碼如下:

decoder.flush().then(() => {
  const width = videoFrames[0].img.width / 2;
  const height = videoFrames[0].img.height / 2;
  const gif = new GIF({
    workers: 4,
    quality: 10,
    width,
    height,
  });

  const halfFrames = videoFrames.filter((frame, index) => index % 2 === 0);
  halfFrames
    .map((frame) => frame.img)
    .forEach((imageBitmap) => {
      const originalWidth = imageBitmap.width;
      const originalHeight = imageBitmap.height;

      var offscreenCanvas = new OffscreenCanvas(
        imageBitmap.width / 2,
        imageBitmap.height / 2
      );
      var offscreenContext = offscreenCanvas.getContext("2d");

      // 在新Canvas上繪制原始ImageBitmap,并縮小一半
      offscreenContext.drawImage(
        imageBitmap,
        0,
        0,
        originalWidth,
        originalHeight,
        0,
        0,
        offscreenCanvas.width,
        offscreenCanvas.height
      );

      const compressedImageData = offscreenContext.getImageData(
        0,
        0,
        offscreenCanvas.width,
        offscreenCanvas.height
      );

      gif.addFrame(compressedImageData, { delay: (1000 / fps) * 2 });
    });

  gif.on("finished", function (blob) {
    // 創(chuàng)建一個(gè)虛擬的下載鏈接并觸發(fā)點(diǎn)擊
    const link = document.createElement("a");
    link.href = URL.createObjectURL(blob);
    link.download = "animated.gif";
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
  });

  // 開(kāi)始生成 GIF
  gif.render();
});

下面是生成的GIF圖,大小在5M左右

最后

decoder.configure(config);中有一個(gè)description字段,搞了好久都沒(méi)搞定,最后還是拜讀了張?chǎng)涡翊罄械奈恼拢虐堰@個(gè)demo跑通。

跑通這個(gè)demo的時(shí)候是十分開(kāi)心的,前端能做的事情越來(lái)越多了,而且Webcodecs解碼的速度非???,希望等到它更加完善后,會(huì)鋪開(kāi)更多的使用場(chǎng)景。

參考

以上就是JavaScript實(shí)現(xiàn)視頻轉(zhuǎn)GIF的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于JavaScript視頻轉(zhuǎn)GIF的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 微信小程序?qū)崿F(xiàn)圖片處理小工具的示例代碼

    微信小程序?qū)崿F(xiàn)圖片處理小工具的示例代碼

    本文將利用微信小程序制作一個(gè)簡(jiǎn)易的圖片處理小工具(自制低配版美圖秀秀),有濾鏡、效果圖和動(dòng)態(tài)濾鏡三個(gè)功能,快跟隨小編一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • bootstrap快速制作后臺(tái)界面

    bootstrap快速制作后臺(tái)界面

    這篇文章主要為大家詳細(xì)介紹了如何利用bootstrap快速制作后臺(tái)界面,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • JavaScrip變量聲明關(guān)鍵字var、let、const詳解

    JavaScrip變量聲明關(guān)鍵字var、let、const詳解

    在JavaScript中var、let和const是用于聲明變量的關(guān)鍵字,但它們之間存在一些關(guān)鍵的區(qū)別,這篇文章主要介紹了JavaScrip變量聲明關(guān)鍵字var、let、const的相關(guān)資料,需要的朋友可以參考下
    2025-04-04
  • springMVC結(jié)合AjaxForm上傳文件

    springMVC結(jié)合AjaxForm上傳文件

    這篇文章主要為大家詳細(xì)介紹了springMVC結(jié)合AjaxForm上傳文件的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-07-07
  • 原生JS實(shí)現(xiàn)百葉窗特效

    原生JS實(shí)現(xiàn)百葉窗特效

    這篇文章主要為大家詳細(xì)介紹了原生JS實(shí)現(xiàn)百葉窗特效,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • 深入理解JavaScript系列(3) 全面解析Module模式

    深入理解JavaScript系列(3) 全面解析Module模式

    Module模式是JavaScript編程中一個(gè)非常通用的模式,一般情況下,大家都知道基本用法,本文嘗試著給大家更多該模式的高級(jí)使用方式
    2012-01-01
  • 微信小程序 調(diào)用遠(yuǎn)程接口 給全局?jǐn)?shù)組賦值代碼實(shí)例

    微信小程序 調(diào)用遠(yuǎn)程接口 給全局?jǐn)?shù)組賦值代碼實(shí)例

    這篇文章主要介紹了微信小程序 調(diào)用遠(yuǎn)程接口 給全局?jǐn)?shù)組賦值代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • JavaScript canvas 中 drawImage函數(shù)及其參數(shù)詳細(xì)介紹

    JavaScript canvas 中 drawImage函數(shù)及其參數(shù)詳細(xì)介紹

    在前端開(kāi)發(fā)里,Canvas的drawImage方法是用于在畫(huà)布上繪制圖像的重要方法,它有三種不同的調(diào)用形式,每種形式對(duì)應(yīng)的參數(shù)含義和功能各有不同,本文介紹JavaScript canvas中drawImage函數(shù)及其參數(shù)介紹,感興趣的朋友參考下吧
    2025-09-09
  • uniapp中獲取位置信息處理步驟

    uniapp中獲取位置信息處理步驟

    在uniapp中獲取位置信息處理,要兼容用戶(hù)同意授權(quán)、拒絕授權(quán)情況下,最終能成功獲取到位置信息的,本文給大家介紹uniapp中獲取位置信息處理步驟,感興趣的朋友跟隨小編一起看看吧
    2024-02-02
  • 微信小程序?qū)崿F(xiàn)列表?xiàng)l件篩選

    微信小程序?qū)崿F(xiàn)列表?xiàng)l件篩選

    這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)列表?xiàng)l件篩選,篩選框的效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-07-07

最新評(píng)論

民勤县| 同江市| 常宁市| 元朗区| 麟游县| 息烽县| 涞水县| 家居| 丰都县| 康定县| 伊宁县| 博乐市| 昆山市| 凤山市| 通江县| 龙岩市| 方城县| 大庆市| 澳门| 昌宁县| 绿春县| 房产| 衡阳市| 达尔| 松潘县| 灯塔市| 宁蒗| 武隆县| 明光市| 筠连县| 兴化市| 永寿县| 灌阳县| 合川市| 汉阴县| 大安市| 本溪市| 龙南县| 张掖市| 蛟河市| 湘西|