亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

前言

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

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

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

視頻解封裝

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

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

這里使用到的是 mp4box.js 這個庫去解碼上傳的視頻文件,以獲取視頻軌道信息。首先定義一個獲取文件 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);

以下大概是一個視頻軌道的字段:

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

獲取視頻幀

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

  • VideoDecoder是一個較新的API,它可以讓我們通過JS在瀏覽器中解碼視頻
  • EncodedVideoChunk是指表示視頻編碼數(shù)據(jù)塊對象,用于表示已經(jīng)編碼的視頻數(shù)據(jù),這些數(shù)據(jù)可以通過網(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錯誤:", 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中我們初始化了一個VideoDecoder,它接收到數(shù)據(jù)之后就會響應(yīng)output回調(diào),在output回調(diào)中我們把videoFrame轉(zhuǎn)成了一個ImageBitmap對象(即幀圖像信息),然后收集起來。
  • 然后我們實(shí)現(xiàn)了一個getChunkList函數(shù)來收集解封裝后的視頻數(shù)據(jù),把所有的chunk收集起來供decoder調(diào)用
  • 兩者配合起來,我們就可以拿到這段視頻軌道的所有視頻幀圖像

合成GIF

當(dāng)所有的視頻幀處理完成之后,docoder會觸發(fā)一個flush方法,我們可以在這里進(jìn)行GIF的合成。這里我GIF合成使用的庫是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("開始");
  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);
  });

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

簡單解釋一下上面的代碼:

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

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

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

所以接下來我們會做如下操作:

  • new GIF的畫布寬高縮小一半
  • 逢兩幀抽取一幀,每一幀的延時變成原來的2
  • 對每一幀進(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)建一個虛擬的下載鏈接并觸發(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);
  });

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

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

最后

decoder.configure(config);中有一個description字段,搞了好久都沒搞定,最后還是拜讀了張鑫旭大佬的文章,才把這個demo跑通。

跑通這個demo的時候是十分開心的,前端能做的事情越來越多了,而且Webcodecs解碼的速度非??欤M鹊剿油晟坪?,會鋪開更多的使用場景。

參考

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

相關(guān)文章

  • javascript的創(chuàng)建多行字符串的7種方法

    javascript的創(chuàng)建多行字符串的7種方法

    多行字符串的作用是用來提高源代碼的可讀性.尤其是當(dāng)你處理預(yù)定義好的較長字符串時,把這種字符串分成多行書寫更有助于提高代碼的可讀性和可維護(hù)性.在一些語言中,多行字符串還可以用來做代碼注釋. 大部分動態(tài)腳本語言都支持多行字符串,比如Python, Ruby, PHP. 但Javascript呢?
    2014-04-04
  • js實(shí)現(xiàn)按鈕控制帶有停頓效果的圖片滾動

    js實(shí)現(xiàn)按鈕控制帶有停頓效果的圖片滾動

    這篇文章主要介紹了js實(shí)現(xiàn)按鈕控制帶有停頓效果的圖片滾動,,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-08-08
  • 淺談javascript實(shí)現(xiàn)八大排序

    淺談javascript實(shí)現(xiàn)八大排序

    排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序,而外部排序是因排序的數(shù)據(jù)很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存。我們這里說說八大排序就是內(nèi)部排序。
    2015-04-04
  • element-ui上傳一張圖片后隱藏上傳按鈕功能

    element-ui上傳一張圖片后隱藏上傳按鈕功能

    這篇文章主要介紹了element-ui上傳一張圖片后隱藏上傳按鈕功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值 ,需要的朋友可以參考下
    2019-05-05
  • JavaScript函數(shù)中this指向問題詳解

    JavaScript函數(shù)中this指向問題詳解

    這篇文章主要給大家介紹了關(guān)于JavaScript函數(shù)中this指向問題的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • swiper自定義分頁器的樣式

    swiper自定義分頁器的樣式

    這篇文章主要為大家詳細(xì)介紹了swiper自定義分頁器的樣式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-09-09
  • Openlayers實(shí)現(xiàn)角度測量的方法

    Openlayers實(shí)現(xiàn)角度測量的方法

    在Openlayers中,雖然沒有直接的角度測量API,但可以通過自定義方法實(shí)現(xiàn),首先,選取三個頂點(diǎn),利用這些點(diǎn)的坐標(biāo)計算夾角度數(shù),接著,用SVG或canvas繪制代表角度的圓弧,并通過Overlay添加到地圖上,本文給大家介紹Openlayers實(shí)現(xiàn)角度測量的方法,感興趣的朋友一起看看吧
    2024-11-11
  • JS實(shí)現(xiàn)AES加密并與PHP互通的方法分析

    JS實(shí)現(xiàn)AES加密并與PHP互通的方法分析

    這篇文章主要介紹了JS實(shí)現(xiàn)AES加密并與PHP互通的方法,結(jié)合實(shí)例形式分析了javascript與php的加密、解密算法相關(guān)操作技巧,需要的朋友可以參考下
    2017-04-04
  • JS中如何比較兩個Json對象是否相等實(shí)例代碼

    JS中如何比較兩個Json對象是否相等實(shí)例代碼

    這篇文章主要介紹了JS中如何比較兩個Json對象是否相等實(shí)例代碼的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-07-07
  • 一步步教你用js簡單實(shí)現(xiàn)新年倒計時

    一步步教你用js簡單實(shí)現(xiàn)新年倒計時

    一轉(zhuǎn)眼已經(jīng)臘月了,相信小伙伴們一定想知道我們距離2023新年還有多少天,下面這篇文章主要給大家介紹了關(guān)于如何一步步教你用js簡單實(shí)現(xiàn)新年倒計時的相關(guān)資料,需要的朋友可以參考下
    2022-12-12

最新評論