Scene
Sometimes I want to process the cutting of some audio files, but I don’t want to download professional software such as Audition. So I opened Baidu to look for such a web tool, and found that most of them only display the time of the audio, without the waveform diagram, which brings inconvenience to the cutting. In order to solve this problem, implement a tool like this yourself
Technology stack
Frontend: ElementUI, wavesurfer.js
Backend: ffmpeg, php
Thoughts
Install ffmpeg on the server, after the front-end uploads the file to the server, the server runs the cutting command and returns the cut file to the front-end for download
The interface is as follows
Front-end code
index.html
<!DOCTYPE html> <html> <head> <title>Audio cutting</title> <script src="//i2.wp.com/unpkg.com/wavesurfer.js"></script> <script src="//i2.wp.com/code.jquery.com/jquery-3.6.0.min.js"></script> <!-- import style --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"> <!-- First introduce Vue --> <script src="//i2.wp.com/cdn.staticfile.org/vue/2.4.2/vue.min.js"></script> <script src="//i2.wp.com/unpkg.com/element-ui/lib/index.js"></script> <!-- Import component library --> <script src="//i2.wp.com/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"> <!-- audio waveform display container --> <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">Click to upload audio file</el-button> <div slot="tip" class="el-upload__tip">Only upload wav/mp3/ogg files, and no more than 100mb</div> </el-upload> </div> <div class="block"> <el-button size="small" type="primary" @click="cutAudio">Cut and export</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() {<!-- --> // Calculate the cutting time point this.$message.error('There is no file that can be cut'); let duration = this.wavesurfer.getDuration(); if(!duration){<!-- --> return; } this.$message('cutting, export later...'); 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) => {<!-- --> // process the response result 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)//Set the downloaded file name a.href = res1.file_path; // Set the download address of the picture a.click();//Trigger download event }, error: (xhr, status, error) => {<!-- --> // handle errors console.log('cutting failed'); 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(`Are you sure to remove ${<!-- -->file.name}?`); } } }) </script>
Code Analysis
1. To display the audio file, I used the library wavesurfer.js, as long as the file path is passed in, the waveform can be displayed
2. The starting and ending points of the interception use the slider component of elementUI,
Drag to select start point and end point
3. I covered a layer of canvas on the waveform graph for drawing the mask, and used the onchange event of the slider to trigger the drawing
Server
Environmental installation
install ffmpeg
Local development, install the windows version, Baidu is enough, this is required
add link description
When putting it on the linux server, Baidu linux ffmpeg method by yourself
php code
upload.php
<?php class UploadFile { /** * upload path * @var string */ public $upload_path = 'uploads'; /** * Obtain a document opening certificate * @param $file * @return mixed|string */ function get_extension($file) { $info = pathinfo($file); return $info['extension']; } /** * Execute the upload * @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']; // Check the MIME type of the uploaded file if (!in_array($file['type'], $allowedMimeTypes)) { exit( json_encode(['code' => 500, 'msg' => 'file type error'])); } $ext = $this->get_extension($_FILES["file"]["name"]); if (!is_dir($this->upload_path)) { mkdir($this->upload_path, 777); } $path = "uploads/" . uniqid() . '.' . $ext; // If the file does not exist in the upload directory, upload the file to the upload directory move_uploaded_file($_FILES["file"]["tmp_name"], $path); exit( json_encode(['code' => 200, 'path' => $path])); } } $upload = new UploadFile(); return $upload->doUpload();
A very common upload class, used to receive files from the front end, and return the server file path for the front end to display waveforms
cut.php
<?php $start_time = isset($_POST['start_time']) ? $_POST['start_time'] : 0; // default is 0 $end_time = isset($_POST['end_time']) ? $_POST['end_time'] : 0; // default is 0 $input_file = isset($_POST['filePath']) ? $_POST['filePath'] : 0; // default is 0 // Define input and output file paths $output_file = 'output/'.date('Y-m-d-H-i-s').'.wav'; // Call FFmpeg for audio cutting $ffmpeg_command = "ffmpeg -i {$input_file} -ss $start_time -to $end_time -c copy {$output_file}"; exec($ffmpeg_command); // Return the cut file path to the front end $response = array('file_path' => $output_file); exit(json_encode($response));
When the export button is clicked, the cutting command of ffmpeg is executed, and the cut file is returned to the front end
Summary
So far, a simple web version of ffmpeg audio cutting has been implemented. ffmpeg has many other functions, such as converting audio and video formats, adding watermarks to videos, etc., you can use this demo to transform
Example project address
https://gitee.com/ghjkg546/ffmpeg-audio-cut