<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## 引言 最近開發的時候遇到了一個需求,截取視頻第一幀作為視頻的封面,結果第一幀是黑屏,所以產品提出,希望可以截取的不是黑屏顏色稍微靚麗幀的作為封面。于是我們進行步驟拆解: 1. 截取第N秒作為視頻封面。 2. 選擇合適的幀作為視頻封面。[](https://link.juejin.cn?target=) ## 注意事項 * 視頻地址必須`同源`或者是`支持跨域訪問`。 * 設置視頻播放時間后,再監聽`canplay`事件。 * 尋找合適幀需要加載時間。 ## 實現步驟 ### 一、獲取視頻基本信息(分辨率、時長) ~~~javascript // 獲取視頻基本信息 function getVideoBasicInfo(videoSrc) { return new Promise((resolve, reject) => { const video = document.createElement('video') video.src = videoSrc // 視頻一定要添加預加載 video.preload = 'auto' // 視頻一定要同源或者必須允許跨域 video.crossOrigin = 'Anonymous' // 監聽:異常 video.addEventListener('error', error => { reject(error) }) // 監聽:加載完成基本信息,設置要播放的時常 video.addEventListener('loadedmetadata', () => { const videoInfo = { video, width: video.videoWidth, height: video.videoHeight, duration: video.duration } resolve(videoInfo) }) }) } 復制代碼 ~~~ ### 二、將視頻繪入canvas用以生成圖片地址 這里需要等待視頻`canplay`事件后,再截取,否則會黑屏 ~~~javascript // 獲取視頻當前幀圖像信息與飽和度 function getVideoPosterInfo(videoInfo) { return new Promise(resolve => { const { video, width, height } = videoInfo video.addEventListener('canplay', () => { const canvas = document.createElement('canvas') canvas.width = width canvas.height = height const ctx = canvas.getContext('2d') // 將視頻對象直接繪入canvas ctx.drawImage(video, 0, 0, width, height) // 獲取圖像的整體平均飽和度 const saturation = getImageSaturation(canvas) const posterUrl = canvas.toDataURL('image/jpg') resolve({ posterUrl, saturation }) }) }) } 復制代碼 ~~~ ### 三、“合適的幀” 這里我們產品提出需要以顏色稍微“靚麗”,經過苦思冥想,何為“靚麗”,眾里尋她千百度,終于尋到“飽和度” > 飽和度:色彩的飽和度(saturation)指色彩的鮮艷程度,也稱作純度。 * 將繪制好的canvas,通過`getImageData`獲取到其`像素數據`。 * 將像素數據整理好成rgba形式的數據。 * 將rgb數據轉換成hsl數據 * 提取hsl數據的s,即飽和度數據,求整體平均值 ? [](https://link.juejin.cn?target=) #### 1、獲取canvas像素數據 這里我們通過調用`getImageData`這個API,獲取像素數據,也就是一整個畫布的每個像素點的顏色。他返回的是一個`Uint8ClampedArray(8位無符號整型固定數組)`,我們可以將其理解成為一個`類數組`,其每0、1、2、3位數據剛好可以對應rgba,即`Uint8ClampedArray[0]`可以對應上`RGBA的R`,以此類推,剛好可以獲取整個畫布的像素顏色情況。 ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ee48732b7ef1406cbfe47f8646bb948a~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp) ~~~javascript // 獲取一個圖片的平均飽和度 function getImageSaturation(canvas) { const ctx = canvas.getContext('2d') const uint8ClampedArray = ctx.getImageData(0, 0, canvas.width, canvas.height).data // .... } 復制代碼 ~~~ #### 2、將Uint8ClampedArray整理成rgba形式 這里我們通過遍歷,根據下標整理數據,轉換成rgba形式,方便后續操作 ? ~~~javascript // 封裝,將無符號整形數組轉換成rgba數組 function binary2rgba(uint8ClampedArray) { const rgbaList = [] for (let i = 0; i < uint8ClampedArray.length; i++) { if (i % 4 === 0) { rgbaList.push({ r: uint8ClampedArray[i] }) continue } const currentRgba = rgbaList[rgbaList.length - 1] if (i % 4 === 1) { currentRgba.g = uint8ClampedArray[i] continue } if (i % 4 === 2) { currentRgba.b = uint8ClampedArray[i] continue } if (i % 4 === 3) { currentRgba.a = uint8ClampedArray[i] continue } } return rgbaList } // 獲取一個圖片的平均飽和度 function getImageSaturation(canvas) { const ctx = canvas.getContext('2d') const uint8ClampedArray = ctx.getImageData(0, 0, canvas.width, canvas.height).data const rgbaList = binary2rgba(uint8ClampedArray) // .... } 復制代碼 ~~~ #### 3、將RGB轉換成HSL,并求平均值 HSL即色相、飽和度、亮度(英語:Hue, Saturation, Lightness)。 色相(H)是色彩的基本屬性,就是平常所說的顏色名稱,如紅色、黃色等。 飽和度(S)是指色彩的純度,越高色彩越純,低則逐漸變灰,取0-100%的數值。 明度(V),亮度(L),取0-100%。 ~~~javascript // 將rgb轉換成hsl function rgb2hsl(r, g, b) { r = r / 255; g = g / 255; b = b / 255; var min = Math.min(r, g, b); var max = Math.max(r, g, b); var l = (min + max) / 2; var difference = max - min; var h, s, l; if (max == min) { h = 0; s = 0; } else { s = l > 0.5 ? difference / (2.0 - max - min) : difference / (max + min); switch (max) { case r: h = (g - b) / difference + (g < b ? 6 : 0); break; case g: h = 2.0 + (b - r) / difference; break; case b: h = 4.0 + (r - g) / difference; break; } h = Math.round(h * 60); } s = Math.round(s * 100);//轉換成百分比的形式 l = Math.round(l * 100); return { h, s, l }; } // 獲取一個圖片的平均飽和度 function getImageSaturation(canvas) { const ctx = canvas.getContext('2d') const uint8ClampedArray = ctx.getImageData(0, 0, canvas.width, canvas.height).data const rgbaList = binary2rgba(uint8ClampedArray) const hslList = rgbaList.map(item => { return rgb2hsl(item.r, item.g, item.b) }) // 求平均值 const avarageSaturation = hslList.reduce((total, curr) => total + curr.s, 0) / hslList.length return avarageSaturation } 復制代碼 ~~~ ### 四、傳入視頻地址與第N秒,獲取第N秒的圖片地址與飽和度 ~~~javascript // 根據視頻地址與播放時長獲取圖片信息與圖片平均飽和度 function getVideoPosterByFrame(videoSrc, targetTime) { return getVideoBasicInfo(videoSrc).then(videoInfo => { const { video, duration } = videoInfo video.currentTime = targetTime return getVideoPosterInfo(videoInfo) }) } 復制代碼 ~~~ ### 五、傳入視頻地址與指定飽和度品質,截取指定飽和度的視頻作為封面 ~~~javascript async function getBestPoster(videoSrc, targetSaturation) { const videoInfo = await getVideoBasicInfo(videoSrc) const { duration } = videoInfo for (let i = 0; i <= duration; i++) { const posterInfo = await getVideoPosterByFrame(videoSrc, i) const { posterUrl, saturation } = posterInfo if (saturation >= targetSaturation) { return posterUrl } } } 復制代碼 ~~~ ## 整體代碼與測試 ~~~javascript // 獲取視頻基本信息 function getVideoBasicInfo(videoSrc) { return new Promise((resolve, reject) => { const video = document.createElement('video') video.src = videoSrc // 視頻一定要添加預加載 video.preload = 'auto' // 視頻一定要同源或者必須允許跨域 video.crossOrigin = 'Anonymous' // 監聽:異常 video.addEventListener('error', error => { reject(error) }) // 監聽:加載完成基本信息,設置要播放的時常 video.addEventListener('loadedmetadata', () => { const videoInfo = { video, width: video.videoWidth, height: video.videoHeight, duration: video.duration } resolve(videoInfo) }) }) } // 將獲取到的視頻信息,轉化為圖片地址 function getVideoPosterInfo(videoInfo) { return new Promise(resolve => { const { video, width, height } = videoInfo video.addEventListener('canplay', () => { const canvas = document.createElement('canvas') canvas.width = width canvas.height = height const ctx = canvas.getContext('2d') ctx.drawImage(video, 0, 0, width, height) const saturation = getImageSaturation(canvas) const posterUrl = canvas.toDataURL('image/jpg') resolve({ posterUrl, saturation }) }) }) } // 獲取一個圖片的平均飽和度 function getImageSaturation(canvas) { const ctx = canvas.getContext('2d') const uint8ClampedArray = ctx.getImageData(0, 0, canvas.width, canvas.height).data console.log(uint8ClampedArray) const rgbaList = binary2rgba(uint8ClampedArray) const hslList = rgbaList.map(item => { return rgb2hsl(item.r, item.g, item.b) }) const avarageSaturation = hslList.reduce((total, curr) => total + curr.s, 0) / hslList.length return avarageSaturation } function rgb2hsl(r, g, b) { r = r / 255; g = g / 255; b = b / 255; var min = Math.min(r, g, b); var max = Math.max(r, g, b); var l = (min + max) / 2; var difference = max - min; var h, s, l; if (max == min) { h = 0; s = 0; } else { s = l > 0.5 ? difference / (2.0 - max - min) : difference / (max + min); switch (max) { case r: h = (g - b) / difference + (g < b ? 6 : 0); break; case g: h = 2.0 + (b - r) / difference; break; case b: h = 4.0 + (r - g) / difference; break; } h = Math.round(h * 60); } s = Math.round(s * 100);//轉換成百分比的形式 l = Math.round(l * 100); return { h, s, l }; } function binary2rgba(uint8ClampedArray) { const rgbaList = [] for (let i = 0; i < uint8ClampedArray.length; i++) { if (i % 4 === 0) { rgbaList.push({ r: uint8ClampedArray[i] }) continue } const currentRgba = rgbaList[rgbaList.length - 1] if (i % 4 === 1) { currentRgba.g = uint8ClampedArray[i] continue } if (i % 4 === 2) { currentRgba.b = uint8ClampedArray[i] continue } if (i % 4 === 3) { currentRgba.a = uint8ClampedArray[i] continue } } return rgbaList } // 根據視頻地址與播放時長獲取圖片信息與圖片平均飽和度 function getVideoPosterByFrame(videoSrc, targetTime) { return getVideoBasicInfo(videoSrc).then(videoInfo => { const { video, duration } = videoInfo video.currentTime = targetTime return getVideoPosterInfo(videoInfo) }) } async function getBestPoster(videoSrc, targetSaturation) { const videoInfo = await getVideoBasicInfo(videoSrc) const { duration } = videoInfo for (let i = 0; i <= duration; i++) { const posterInfo = await getVideoPosterByFrame(videoSrc, i) const { posterUrl, saturation } = posterInfo // 判斷當前飽和度是否大于等于期望的飽和度 if (saturation >= targetSaturation) { return posterUrl } } } // 這里通過http-server將視頻地址與js進行同源 const videoSrc = 'http://192.168.2.1:8081/trailer.mp4' // 飽和度品質 0/10/30/50 const targetSaturation = 0 getBestPoster(videoSrc, targetSaturation).then(posterUrl => { const image = new Image() image.src = posterUrl document.body.append(image) }).catch(error => { console.log(error) }) 復制代碼 ~~~ ### 飽和度:0 ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cbc9ee9066ac4f73bf666d6262609187~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp)[](https://link.juejin.cn?target=) ### 飽和度:10 ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f7aa9d86eb3744e08c0690293e24cd82~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp)[](https://link.juejin.cn?target=) ### 飽和度:30 ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3299634335fa49b0b4c395c5ce9ab443~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp)[](https://link.juejin.cn?target=) ### 飽和度:50 ![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b30ba0ce595345bf8e1a331813f850be~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.awebp)[](https://link.juejin.cn?target=)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看