【视频混剪Demo】FFmpeg的使用【Windows】

#1024程序员节 | 征文# 

目录

一、简介

二、音频素材页

2.1 功能描述

👉 搜索

👉 添加

👉 删除

2.2 效果展示

2.3 代码实现 

👉 前端

👉 后端

三、视频素材页

3.1 功能描述

👉 搜索

👉 添加

👉 编辑

👉 删除

3.2 效果展示

3.3 代码实现

👉 前端

👉 后端

四、分组管理页

4.1 功能描述

👉 搜索

👉 添加

👉 编辑

👉 删除

4.2 效果展示

4.3 代码实现

👉 前端

👉 后端

五、视频混剪页

5.1 功能描述

👉 搜索

👉 添加

👉 删除

5.2 效果展示

5.3 代码实现

👉 前端

👉 后端


一、简介

此篇文章带来的是 使用 FFmpeg 实现视频混剪并且可以指定生成视频的数量以及生成视频的时长,此案例也是对前边几篇文章功能的综合。

说明:

此案例中的代码部分,前端使用的是 Vue + ElementPlus + Vite、后端使用的是 Nodejs + FFmpeg + MySQL

👇下方提供的代码为每一部分的核心代码,如果需要案例的完整代码,可以在评论区留言我来私信你!!!👆

二、音频素材页

2.1 功能描述

👉 搜索

在搜索栏中输入 音频名称、上传时间 后点击搜索按钮即可,也可以只输入一个条件搜索。

👉 添加

点击页面上的【添加音频】按钮,然后在弹出的上传音频对话框中输入 音频名称,并选择要上传的音频文件之后直接点击确定按钮即可完成音频文件的上传。

👉 删除

点击表格中的【删除】按钮即可删除对应的音频文件

2.2 效果展示

音频素材页面
点击上一张图片中的添加音频按钮弹出的对话框
选择要上传的音频文件

2.3 代码实现 

👉 前端

<template>
  <div>
    <el-button type="primary" @click="dialogVisible = true">添加音频</el-button>
    <el-row style="margin: 10px 0">
      <el-form v-model="queryParams" inline>
        <el-form-item label="音频名称">
          <el-input
            v-model="queryParams.name"
            placeholder="输入要查询的文件名称"
            style="width: 200px"
          ></el-input>
        </el-form-item>
        <el-form-item label="上传时间">
          <el-date-picker
            v-model="daterange"
            type="daterange"
            unlink-panels
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            :shortcuts="shortcuts"
          />
        </el-form-item>
      </el-form>
      <div style="margin-left: 20px">
        <el-button type="primary" @click="handleSearch">搜索</el-button>
        <el-button type="info" plain @click="handleReset">重置</el-button>
      </div>
    </el-row>
    <el-table
      :data="tableData"
      stripe
      border
      :style="{ height: tableHeight ? tableHeight + 'px' : '' }"
    >
      <el-table-column prop="id" label="ID" align="center" width="80" />
      <el-table-column prop="name" label="音频名称" align="center" />
      <el-table-column
        prop="path"
        label="音频文件"
        align="center"
        min-width="260"
      >
        <template #default="scope">
          <audio :src="scope.row.path" controls class="audioStyle"></audio>
        </template>
      </el-table-column>
      <el-table-column
        prop="type"
        label="音频类型"
        align="center"
        width="100"
      />
      <el-table-column prop="size" label="文件大小" align="center" width="90">
        <template #default="scope"> {
  { scope.row.size }}M </template>
      </el-table-column>
      <el-table-column
        prop="upload_time"
        label="上传时间"
        align="center"
        width="200"
      />
      <el-table-column label="操作" align="center" width="90" fixed="right">
        <template #default="scope">
          <el-button
            v-if="choose"
            type="success"
            size="small"
            @click="handleChoose(scope.row)"
            >选择</el-button
          >
          <el-button
            v-else
            type="danger"
            size="small"
            @click="handleDelete(scope.row)"
            >删除</el-button
          >
        </template>
      </el-table-column>
    </el-table>
    <el-row style="margin-top: 10px; justify-content: end; align-items: center">
      <span style="margin-right: 20px; color: #606266">共 {
  { total }} 条</span>
      <el-pagination
        background
        layout="prev, pager, next"
        :page-size="queryParams.size"
        :total="total"
        @current-change="handleCurrentChange"
      ></el-pagination>
    </el-row>
    <!-- 添加音频对话框 -->
    <el-dialog v-model="dialogVisible" title="上传音频">
      <el-form :model="formData" :rules="rules" ref="ruleFormRef">
        <el-form-item label="音频名称" prop="name">
          <el-input
            v-model="formData.name"
            placeholder="请输入音频名称"
          ></el-input>
        </el-form-item>
        <el-form-item label="音频文件" prop="file">
          <el-input
            v-model="formData.path"
            placeholder="请选择音频文件"
            class="input-with-select"
            disabled
          >
            <template #append>
              <el-upload
                v-model:file-list="fileList"
                action=""
                :limit="1"
                :before-upload="beforeUpload"
                :show-file-list="false"
                accept="audio/*"
              >
                <el-button :icon="Upload" class="upload-btn">上传</el-button>
              </el-upload>
            </template>
          </el-input>
        </el-form-item>
      </el-form>

      <template #footer>
        <div class="dialog-footer">
          <el-button @click="handleCancel">取消</el-button>
          <el-button type="primary" @click="handleSubmit"> 确定 </el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import { onMounted, ref } from "vue";
import { Upload } from "@element-plus/icons-vue";
import { post, get, del } from "@/utils/http";
import { useStore } from "@/store";

const porp = defineProps(["choose"]);
const store = useStore();

const tableData = ref([]);
const total = ref(0);
// 查询条件
const queryParams = ref({
  name: undefined,
  startTime: undefined,
  endTime: undefined,
  page: 1,
  size: 10,
});
const daterange = ref([]);
const shortcuts = [
  {
    text: "最近一周",
    value: () => {
      const end = new Date();
      const start = new Date();
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
      return [start, end];
    },
  },
  {
    text: "最近一个月",
    value: () => {
      const end = new Date();
      const start = new Date();
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
      return [start, end];
    },
  },
  {
    text: "最近三个月",
    value: () => {
      const end = new Date();
      const start = new Date();
      start.setTime(start.getTime() - 3600 * 1000 * 24 * 90);
      return [start, end];
    },
  },
];
// 获取音频列表
const getList = () => {
  get("/audio/list", queryParams.value).then((res) => {
    tableData.value = res.data;
    total.value = res.total;
  });
};
// 搜索
const handleSearch = () => {
  queryParams.value.startTime = daterange.value[0];
  queryParams.value.endTime = daterange.value[1];
  console.log(queryParams.value);
  getList();
};
// 重置
const handleReset = () => {
  queryParams.value = {
    name: undefined,
    startTime: undefined,
    endTime: undefined,
    page: 1,
    size: 10,
  };
  daterange.value = [];
  getList();
};
// 切换页
const handleCurrentChange = (page) => {
  queryParams.value.page = page;
  getList();
};

const dialogVisible = ref(false); //弹出对话框是否显示
const fileList = ref([]);
const ruleFormRef = ref(null);
const formData = ref({
  name: "",
  path: "",
  file: "",
});
// 校验规则
const rules = {
  name: [{ required: true, message: "请输入音频名称", trigger: "blur" }],
  file: [
    { required: true, message: "请选择要上传的音频文件", trigger: "blur" },
  ],
};
// 点击取消按钮
const handleCancel = () => {
  dialogVisible.value = false;
  // 重置表单
  formData.value = {
    name: "",
    file: "",
  };
};
// 点击确定按钮
const handleSubmit = () => {
  const data = new FormData();
  data.append("name", formData.value.name);
  data.append("audio", formData.value.file);
  console.log(formData.value);
  // 先进行表单的验证
  ruleFormRef.value.validate((valid) => {
    if (valid) {
      post("/audio/single/audio", data).then((res) => {
        ElMessage.success("上传成功");
        console.log("上传成功", res);
        dialogVisible.value = false;
        getList();
      });
    }
  });
};

// 上传文件之前
const beforeUpload = (file) => {
  formData.value.path = URL.createObjectURL(file);
  formData.value.file = file;
  return false;
};

// 删除
const handleDelete = (item) => {
  del(`/audio/delete/${item.id}`).then((res) => {
    ElMessage.success("删除成功");
    getList();
  });
};
// 选择音频
const handleChoose = (item) => {
  store.chooseBgm(item);
  store.setAudioDialog();
};
onMounted(() => {
  getList();
});
</script>

<style lang="scss" scoped>
.el-table {
  margin-top: 10px;
}
.el-button.upload-btn {
  height: 100%;
  background: #f56c6c;
  color: #fff;
  &:hover {
    background: #f89898;
    color: #fff;
  }
}
.el-input-group__append > div {
  display: flex;
}
.audioStyle {
  width: 250px;
  height: 40px;
}
</style>

👉 后端

var express = require('express');
var router = express.Router();
const multer = require('multer');
const path = require('path');
const connection = require('../config/db.config')
const baseURL = 'http://localhost:3000'

// 音频
const uploadVoice = multer({
  dest: 'public/uploadVoice/',
  storage: multer.diskStorage({
    destination: function (req, file, cb) {
      cb(null, 'public/uploadVoice'); // 文件保存的目录
    },
    filename: function (req, file, cb) {
      // 提取原始文件的扩展名
      const ext = path.extname(file.originalname).toLowerCase(); // 获取文件扩展名,并转换为小写
      // 生成唯一文件名,并加上扩展名
      const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
      const fileName = uniqueSuffix + ext; // 新文件名
      cb(null, fileName); // 文件名
    }
  })
});

// 上传单个音频文件
router.post('/single/audio', uploadVoice.single('audio'), (req, res) => {
  const audioPath = req.file.path.replace('public', '').replace(/\\/g, '/');
  const { name } = req.body
  const type = req.file.filename.split('.')[1]
  const size = (req.file.size / (1024 * 1024)).toFixed(2)
  console.log(req.file)
  console.log(name, audioPath, type, size)
  const insertSql = 'insert into audio (name,path,type,size) values (?,?,?,?)'
  const insertValues = [name, audioPath, type, size]
  connection.query(insertSql, insertValues, (err, results) => {
    if (err) {
      console.error('插入数据失败:', err.message);
      res.send({
        status: 500,
        msg: err.message
      })
      return;
    }
    console.log('音频插入成功');
    res.send({
      status: 200,
      msg: 'ok',
      path: audioPath// 返回新插入记录的ID
    })
  })
})

// 获取音频列表
router.get('/list', (req, res) => {
  let { name, startTime, endTime, page, size, isAll } = req.query
  startTime = startTime ? (new Date(startTime)).toLocaleDateString() : ''
  endTime = endTime ? (new Date(endTime)).toLocaleDateString() : ''
  console.log({ name, startTime, endTime, page, size })

  let selectSql = ''
  // 按照名称查询并按照id进行倒序排列
  if (name && !startTime) {
    selectSql = `select * from audio where name like '%${name}%' order by id desc`
  } else if (!name && startTime) {
    // 按照上传时间并按照id进行倒序排列
    selectSql = `select * from audio where Date(upload_time) between '${startTime}' and '${endTime}' order by id desc`
  } else if (name && startTime) {
    // 按照时间时间和名称并按照id进行倒序排列
    selectSql = `select * from audio where name like '%${name}%' and Date(upload_time) between '${startTime}' and '${endTime}' order by id desc`
  } else {
    selectSql = 'select * from audio order by id desc'
  }

  connection.query(selectSql, (err, results) => {
    if (err) {
      console.error('查询数据失败:', err.message);
      res.send({
        status: 500,
        msg: err.message
      })
      return;
    }
    const data = results.map(item => {
      item.path = baseURL + item.path
      item['upload_time'] = item['upload_time'].toLocaleString()
      return item
    })
    res.send({
      status: 200,
      msg: 'ok',
      data: isAll ? data : data.slice((page - 1) * size, page * size),
      total: data.length
    })
  })
})

// 删除音频
router.delete('/delete/:id', (req, res) => {
  const { id } = req.params
  const deleteSql = `delete from audio where id = ?`
  connection.query(deleteSql, [id], (err, results) => {
    if (err) {
      console.error('删除数据失败:', err.message);
      res.send({
        status: 500,
        msg: err.message
      })
      return;
    }
    console.log('音频删除成功')
    res.send({
      status: 200,
      msg: 'ok',
    })
  })
})

module.exports = router;

三、视频素材页

3.1 功能描述

👉 搜索

在搜索栏中输入 视频名称、视频分组、上传时间 后点击搜索按钮即可,也可以只输入一个或者两个条件搜索。

👉 添加

点击页面上的【上传视频】按钮,然后在弹出的上传视频对话框中输入 视频名称、选择 视频分组(选填),并选择要上传的视频文件之后,点击确定按钮即可完成视频文件的上传。

👉 编辑

点击页面中的【编辑】按钮,即可在弹出的对话框中编辑该条视频的 名称和分组

👉 删除

点击页面中的【删除】按钮,即可删除对应的视频文件。

3.2 效果展示

视频素材页面
点击上一张图片中的上传视频按钮弹出的对话框
选择要上传的视频文件

3.3 代码实现

👉 前端

<template>
  <div>
    <el-button type="primary" @click="handleAdd">上传视频</el-button>
    <el-row style="margin-top: 10px">
      <el-form v-model="queryParams" inline>
        <el-form-item label="视频名称">
          <el-input
            v-model="queryParams.name"
            placeholder="输入要查询的文件名称"
            style="width: 200px"
          ></el-input>
        </el-form-item>
        <el-form-item label="视频分组">
          <el-select
            v-model="queryParams.group_id"
            clearable
            placeholder="请选择分组"
            style="width: 200px"
          >
            <el-option
              v-for="item in groups"
              :key="item.id"
              :label="item.group_name"
              :value="item.id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="上传时间">
          <el-date-picker
            v-model="daterange"
            type="daterange"
            unlink-panels
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
            :shortcuts="shortcuts"
          />
        </el-form-item>
      </el-form>
      <div style="margin-left: 20px">
        <el-button type
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值