From 35f62fdeedf538fbf9d20c34ee2a0646adf0a600 Mon Sep 17 00:00:00 2001 From: KuaiYu95 Date: Fri, 5 Jan 2024 11:33:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A7=86=E9=A2=91=E6=8F=90=E5=8F=96?= =?UTF-8?q?=E9=9F=B3=E9=A2=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/getvideoaudio.tsx | 147 ++++++++++++++++++++++++++++++++++++ src/utils/bufferToWave.ts | 70 +++++++++++++++++ src/utils/tags.ts | 9 +++ src/utils/tools.ts | 7 ++ 4 files changed, 233 insertions(+) create mode 100644 src/pages/getvideoaudio.tsx create mode 100644 src/utils/bufferToWave.ts diff --git a/src/pages/getvideoaudio.tsx b/src/pages/getvideoaudio.tsx new file mode 100644 index 0000000..347b13d --- /dev/null +++ b/src/pages/getvideoaudio.tsx @@ -0,0 +1,147 @@ +import MainContent from '@/components/MainContent'; +import bufferToWave from '@/utils/bufferToWave'; +import { Box, Button, Stack, TextField } from '@mui/material'; +import { useState } from 'react'; + +const _C = () => { + const [audioUrl, setAudioUrl] = useState(''); + const [url, setUrl] = useState(''); + const [name, setName] = useState(''); + const [err, setError] = useState(''); + + const handleBuffer = (buffer: ArrayBuffer, name: string) => { + if (/\//.test(name)) { + name = name.split('/').slice(-1)[0]; + } + name = name.split('.')[0]; + setName(name); + + const audioCtx = new AudioContext(); + audioCtx.decodeAudioData(buffer, function (audioBuffer) { + const blob = bufferToWave( + audioBuffer, + audioBuffer.sampleRate * audioBuffer.duration + ); + const url = URL.createObjectURL(blob); + setAudioUrl(url); + }); + }; + + const convert = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + setUrl(''); + const reader = new FileReader(); + reader.onload = function (e) { + if (e.target !== null) { + const arrBuffer = e.target.result; + handleBuffer(arrBuffer as ArrayBuffer, file.name); + } + }; + reader.readAsArrayBuffer(file); + } + }; + + const upload = () => { + const fileInput = document.getElementById('fileInput'); + fileInput?.click(); + }; + + const getUrl = () => { + if (url) { + fetch(url) + .then((res) => res.arrayBuffer()) + .then((res) => handleBuffer(res, name)) + .catch((err) => setError(String(err))); + } + }; + + return ( + + + 1. 本地视频 + + 2. 网络视频 + + setUrl(e.target.value)} + /> + + + {audioUrl && ( + + 试听音频 + + + + 点击此处下载音频 + + + + )} + {err && ( + + {err} + + )} + + + + ); +}; + +export default _C; diff --git a/src/utils/bufferToWave.ts b/src/utils/bufferToWave.ts new file mode 100644 index 0000000..1abddb6 --- /dev/null +++ b/src/utils/bufferToWave.ts @@ -0,0 +1,70 @@ +// Convert AudioBuffer to a Blob using WAVE representation +export default function bufferToWave(abuffer: AudioBuffer, len: number) { + var numOfChan = abuffer.numberOfChannels, + length = len * numOfChan * 2 + 44, + buffer = new ArrayBuffer(length), + view = new DataView(buffer), + channels = [], + i, + sample, + offset = 0, + pos = 0; + + // write WAVE header + // "RIFF" + setUint32(0x46464952); + // file length - 8 + setUint32(length - 8); + // "WAVE" + setUint32(0x45564157); + // "fmt " chunk + setUint32(0x20746d66); + // length = 16 + setUint32(16); + // PCM (uncompressed) + setUint16(1); + setUint16(numOfChan); + setUint32(abuffer.sampleRate); + // avg. bytes/sec + setUint32(abuffer.sampleRate * 2 * numOfChan); + // block-align + setUint16(numOfChan * 2); + // 16-bit (hardcoded in this demo) + setUint16(16); + // "data" - chunk + setUint32(0x61746164); + // chunk length + setUint32(length - pos - 4); + + // write interleaved data + for (i = 0; i < abuffer.numberOfChannels; i++) + channels.push(abuffer.getChannelData(i)); + + while (pos < length) { + // interleave channels + for (i = 0; i < numOfChan; i++) { + // clamp + sample = Math.max(-1, Math.min(1, channels[i][offset])); + // scale to 16-bit signed int + sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0; + // write 16-bit sample + view.setInt16(pos, sample, true); + pos += 2; + } + // next source sample + offset++; + } + + // create Blob + return new Blob([buffer], { type: 'audio/wav' }); + + function setUint16(data: number) { + view.setUint16(pos, data, true); + pos += 2; + } + + function setUint32(data: number) { + view.setUint32(pos, data, true); + pos += 4; + } +} diff --git a/src/utils/tags.ts b/src/utils/tags.ts index a53b4bc..3f2ec8f 100644 --- a/src/utils/tags.ts +++ b/src/utils/tags.ts @@ -24,6 +24,7 @@ export enum Tags { OTHER = 'other', HOT = 'hot', LIKE = 'like', + MEDIA = 'media', IMAGE = 'image', BINARY = 'binary', SECURITY = 'security', @@ -137,6 +138,14 @@ export const allTags: Tag[] = [ bg_color: '', avatar_color: '', }, + { + name: Tags.MEDIA, + icon: json, + icon_check: json_check, + label: '视频音频', + bg_color: '', + avatar_color: '', + }, { name: Tags.BINARY, icon: dev, diff --git a/src/utils/tools.ts b/src/utils/tools.ts index 748218c..7329b81 100644 --- a/src/utils/tools.ts +++ b/src/utils/tools.ts @@ -409,4 +409,11 @@ export const allTools: Tool[] = [ key: [], subTitle: '支持识别中文、英语、俄语、德语、法语、日语、韩语', }, + { + label: '视频提取音频', + tags: [Tags.MEDIA], + path: '/getvideoaudio', + key: [], + subTitle: '视频提取音频小工具', + }, ];