场景
有时想处理一些音频文件的切割,又不想专门下载audition之类的专业软件。于是我就打开百度寻找这样的网页工具,发现大部分只显示音频的时间,不带波形图,给切割带来不便。为了解决这个问题,那就自己实现一个这样的工具吧
技术栈
前端:ElementUI,wavesurfer.js
后端:ffmpeg,php
思路
服务端安装ffmpeg,前端上传文件到服务器之后,服务端运行切割命令,返回切割好的文件给前端下载
界面如下
前端代码
index.html
<!DOCTYPE html>
<html>
<head>
<title>音频切割</title>
<script src="https://unpkg.com/wavesurfer.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 先引入 Vue -->
<script src="https://cdn.staticfile.org/vue/2.4.2/vue.min.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<style>
#waveform {
width: 600px;
height: 200px;
position: relative;
}
.block {
margin-bottom: 20px;
}
.overlay {
position: absolute;
width: 600px;
height: 200px;
z-index: 100;
border: 1px solid #0B102C;
opacity: 0.3;
}
</style>
</head>
<body>
<div id="app">
<!-- 音频波形展示容器 -->
<div class="block">
<div id="waveform">
<canvas id="myCanvas" width="600" height="100" class="overlay"
style="border:1px solid #000000;">
</canvas>
</div>
</div>
<div class="block">
<el-slider
style="width: 600px"
v-model="rangeValue"
range
show-stops
@change="changeSlider"
:max="1000">
</el-slider>
</div>
<div class="block">
<el-upload
class="upload-demo"
action="upload.php"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:on-success="uploadSuccess"
:file-list="fileList">
<el-button size="small" type="primary">点击上传音频文件</el-button>
<div slot="tip" class="el-upload__tip">只能上传wav/mp3/ogg文件,且不超过100mb</div>
</el-upload>
</div>
<div class="block">
<el-button size="small" type="primary" @click="cutAudio">剪裁并导出</el-button>
</div>
</div>
</body>
</html>
<script>
new Vue({
el: '#app',
data() {
return {
divWidth: 600,
overlayLeft: '50px',
filePath: '',
maxValue: 1000,
audioDuration: 0,
rangeValue: [1, 1000],
wavesurfer: null,
fileList: []
};
},
mounted() {
this.wavesurfer = WaveSurfer.create({
container: '#waveform',
waveColor: 'violet',
progressColor: 'purple'
});
this.drawRect(0, 0, this.divWidth, 100)
},
methods: {
drawRect(x, y, w, h) {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.clearRect(0, 0, c.width, c.height)
ctx.fillStyle = "#FF0000";
ctx.fillRect(x, y, w, 100);
},
cutAudio() {
// 计算切割时间点
this.$message.error('没有可以切割的文件');
let duration = this.wavesurfer.getDuration();
if(!duration){
return ;
}
this.$message('切割中,稍后导出...');
let startTime = duration * parseFloat(this.rangeValue[0]) / this.maxValue;
let endTime = duration * parseFloat(this.rangeValue[1]) / this.maxValue;
$.ajax({
url: 'cut.php',
method: 'POST',
data: {start_time: startTime, end_time: endTime, filePath: this.filePath},
success: (res) => {
// 处理响应结果
let res1 = JSON.parse(res)
let a = document.createElement('a')
var filePath = res1.file_path
let path = filePath.lastIndexOf('/')
a.download = filePath.substr(path + 1)//设置下载的文件名
a.href = res1.file_path; // 设置图片的下载地址
a.click();//触发下载事件
},
error: (xhr, status, error) => {
// 处理错误
console.log('切割失败');
console.log(error);
}
});
},
changeSlider(e) {
let x = Math.ceil(this.rangeValue[0] / this.maxValue * this.divWidth)
let tmpWidth = this.rangeValue[1] - this.rangeValue[0];
let width = Math.ceil(tmpWidth / this.maxValue * this.divWidth)
this.drawRect(x, 0, width, 100)
},
uploadSuccess(res, file, fileLis) {
if(res.code ==500){
this.$message.error(res.msg);
this.fileList = [];
return;
}
this.wavesurfer.load(res.path);
this.filePath = res.path
this.audioDuration = this.wavesurfer.getDuration();
},
handleRemove(file, fileList) {
console.log(file, fileList);
},
handlePreview(file) {
console.log(file);
},
beforeRemove(file, fileList) {
return this.$confirm(`确定移除 ${file.name}?`);
}
}
})
</script>
代码解析
1.音频文件的显示我用到了wavesurfer.js这个库,只要传入文件路径即可显示波形
2.截取的起点终点用到了elementUI的slider组件,
拖动选择起点终点
3.我在波形图上覆盖了一层canvas,用于绘制遮罩,用slider的onchange事件触发绘制
服务端
环境安装
安装ffmpeg
本地开发,安装windows版本的,百度即可,必须这个
添加链接描述
放到linux服务器上时,自行百度linux ffmpeg方法
php代码
upload.php
<?php
class UploadFile
{
/**
* 上传路径
* @var string
*/
public $upload_path = 'uploads';
/**
* 获取文件开证明
* @param $file
* @return mixed|string
*/
function get_extension($file)
{
$info = pathinfo($file);
return $info['extension'];
}
/**
* 执行上传
* @return false|string
*/
function doUpload()
{
$file = $_FILES['file'];
if ($_FILES["file"]["error"] > 0) {
exit( json_encode(['code' => 500, 'msg' => $_FILES["file"]["error"]]));
}
$allowedMimeTypes = ['audio/mpeg', 'audio/ogg', 'audio/wav'];
// 检查上传文件的 MIME 类型
if (!in_array($file['type'], $allowedMimeTypes)) {
exit( json_encode(['code' => 500, 'msg' => '文件类型错误']));
}
$ext = $this->get_extension($_FILES["file"]["name"]);
if (!is_dir($this->upload_path)) {
mkdir($this->upload_path, 777);
}
$path = "uploads/" . uniqid() . '.' . $ext;
// 如果 upload 目录不存在该文件则将文件上传到 upload 目录下
move_uploaded_file($_FILES["file"]["tmp_name"], $path);
exit( json_encode(['code' => 200, 'path' => $path]));
}
}
$upload = new UploadFile();
return $upload->doUpload();
一个很常见的上传类,用于接收前端传过来的文件,并返回服务端文件路径用于前端展示波形
cut.php
<?php
$start_time = isset($_POST['start_time']) ? $_POST['start_time'] : 0; // 默认为 0
$end_time = isset($_POST['end_time']) ? $_POST['end_time'] : 0; // 默认为 0
$input_file = isset($_POST['filePath']) ? $_POST['filePath'] : 0; // 默认为 0
// 定义输入和输出文件路径
$output_file = 'output/'.date('Y-m-d-H-i-s').'.wav';
// 调用 FFmpeg 进行音频切割
$ffmpeg_command = "ffmpeg -i {$input_file} -ss $start_time -to $end_time -c copy {$output_file}";
exec($ffmpeg_command);
// 返回切割后的文件路径给前端
$response = array('file_path' => $output_file);
exit(json_encode($response));
点击导出按钮时,执行ffmpeg的切割命令,并且返回切割好的文件给前端
总结
至此,一个简单的ffmpeg音频切割web版已实现,ffmpeg还有很多其他功能,音视频转格式,给视频加水印等等,可以用这个demo去改造
示例项目地址
https://gitee.com/ghjkg546/ffmpeg-audio-cut