Express.js视频处理:视频上传与转码处理

【免费下载链接】express 【免费下载链接】express 项目地址: https://gitcode.com/gh_mirrors/exp/express

你还在为视频上传速度慢、转码效率低而烦恼吗?本文将带你使用Express.js构建一个高效的视频处理系统,从文件上传到格式转码,全程手把手教学,让你轻松应对各类视频处理需求。读完本文,你将掌握:视频上传的核心原理、大文件分片上传技巧、基于FFmpeg的视频转码实现以及处理进度监控方法。

视频上传基础

在Express.js中实现文件上传,通常需要借助中间件来处理multipart/form-data类型的请求。虽然官方示例中没有直接提供视频上传的代码,但我们可以参考下载功能示例的文件处理逻辑,结合第三方上传中间件来实现视频上传功能。

以下是一个基础的视频上传实现,使用multer中间件处理文件上传:

const express = require('express');
const multer = require('multer');
const app = express();

// 配置存储引擎
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/')
  },
  filename: function (req, file, cb) {
    // 为视频文件生成唯一文件名
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    cb(null, file.fieldname + '-' + uniqueSuffix + '.mp4');
  }
});

// 文件过滤,只允许视频文件
const fileFilter = (req, file, cb) => {
  if (file.mimetype.startsWith('video/')) {
    cb(null, true);
  } else {
    cb(new Error('只允许上传视频文件!'), false);
  }
};

const upload = multer({ storage: storage, fileFilter: fileFilter });

// 视频上传接口
app.post('/upload', upload.single('video'), (req, res) => {
  res.json({
    message: '视频上传成功',
    file: req.file
  });
});

app.listen(3000, () => {
  console.log('视频上传服务器启动,端口3000');
});

大文件分片上传

对于大型视频文件,直接上传容易出现超时或失败的情况。实现分片上传可以有效解决这一问题,将大文件分割成多个小片段分别上传,最后在服务器端合并。

// 分片上传实现示例
const fs = require('fs');
const path = require('path');
const { createWriteStream } = require('fs');
const { Readable } = require('stream');

// 合并分片
const mergeChunks = async (filePath, chunksDir, totalChunks) => {
  const writeStream = createWriteStream(filePath);
  
  for (let i = 0; i < totalChunks; i++) {
    const chunkPath = path.join(chunksDir, `${i}`);
    const chunk = fs.readFileSync(chunkPath);
    await new Promise(resolve => {
      Readable.from(chunk).pipe(writeStream, { end: false });
      writeStream.once('finish', resolve);
    });
    fs.unlinkSync(chunkPath);
  }
  
  fs.rmdirSync(chunksDir);
  writeStream.end();
};

// 分片上传接口
app.post('/upload/chunk', upload.single('chunk'), (req, res) => {
  const { filename, chunkIndex, totalChunks } = req.body;
  const chunksDir = path.join(__dirname, 'chunks', filename);
  
  if (!fs.existsSync(chunksDir)) {
    fs.mkdirSync(chunksDir, { recursive: true });
  }
  
  const chunkPath = path.join(chunksDir, chunkIndex);
  fs.renameSync(req.file.path, chunkPath);
  
  // 检查是否所有分片都已上传
  const uploadedChunks = fs.readdirSync(chunksDir).length;
  if (uploadedChunks === parseInt(totalChunks)) {
    const finalPath = path.join(__dirname, 'uploads', filename);
    mergeChunks(finalPath, chunksDir, totalChunks);
    res.json({ message: '文件合并完成' });
  } else {
    res.json({ message: '分片上传成功', uploaded: uploadedChunks });
  }
});

视频转码处理

视频转码是视频处理中的关键环节,我们可以使用FFmpeg工具来实现视频格式转换、分辨率调整等功能。以下是一个基于Express.js和FFmpeg的视频转码实现:

const { exec } = require('child_process');
const path = require('path');

// 视频转码函数
const transcodeVideo = (inputPath, outputPath, options, callback) => {
  const command = `ffmpeg -i ${inputPath} ${options} ${outputPath}`;
  
  exec(command, (error, stdout, stderr) => {
    if (error) {
      console.error(`转码错误: ${error.message}`);
      return callback(error);
    }
    if (stderr) {
      console.log(`转码输出: ${stderr}`);
    }
    callback(null, outputPath);
  });
};

// 转码接口
app.post('/transcode', (req, res) => {
  const { inputFile, format = 'mp4', quality = '720p' } = req.body;
  const inputPath = path.join(__dirname, 'uploads', inputFile);
  
  // 根据质量设置转码参数
  let options = '';
  switch(quality) {
    case '1080p':
      options = '-s 1920x1080 -b:v 5000k';
      break;
    case '720p':
      options = '-s 1280x720 -b:v 2500k';
      break;
    case '480p':
      options = '-s 854x480 -b:v 1000k';
      break;
    default:
      options = '-s 1280x720 -b:v 2500k';
  }
  
  const outputFile = `${path.parse(inputFile).name}_${quality}.${format}`;
  const outputPath = path.join(__dirname, 'transcoded', outputFile);
  
  transcodeVideo(inputPath, outputPath, options, (error, result) => {
    if (error) {
      return res.status(500).json({ error: error.message });
    }
    res.json({
      message: '视频转码成功',
      outputFile: outputFile,
      path: outputPath
    });
  });
});

完整视频处理流程

将上传和转码功能结合起来,我们可以构建一个完整的视频处理流程:

// 完整视频处理流程
app.post('/process-video', upload.single('video'), (req, res) => {
  const inputFile = req.file.filename;
  const inputPath = req.file.path;
  
  // 转码为多种分辨率
  const qualities = ['480p', '720p', '1080p'];
  const transcodingPromises = qualities.map(quality => {
    return new Promise((resolve, reject) => {
      const outputFile = `${path.parse(inputFile).name}_${quality}.mp4`;
      const outputPath = path.join(__dirname, 'transcoded', outputFile);
      const options = quality === '1080p' ? 
        '-s 1920x1080 -b:v 5000k' : 
        quality === '720p' ? '-s 1280x720 -b:v 2500k' : 
        '-s 854x480 -b:v 1000k';
      
      transcodeVideo(inputPath, outputPath, options, (error, result) => {
        if (error) reject(error);
        else resolve(outputFile);
      });
    });
  });
  
  Promise.all(transcodingPromises)
    .then(outputFiles => {
      res.json({
        message: '视频处理完成',
        originalFile: inputFile,
        transcodedFiles: outputFiles
      });
    })
    .catch(error => {
      res.status(500).json({ error: error.message });
    });
});

进度监控与错误处理

为提升用户体验,我们需要实现视频处理进度监控和完善的错误处理机制:

// 进度监控实现
const { spawn } = require('child_process');

const transcodeWithProgress = (inputPath, outputPath, options, onProgress, callback) => {
  const command = `ffmpeg -i ${inputPath} ${options} ${outputPath}`;
  const ffmpeg = spawn('ffmpeg', ['-i', inputPath, ...options.split(' '), outputPath]);
  
  ffmpeg.stderr.on('data', (data) => {
    const output = data.toString();
    // 解析转码进度
    const match = output.match(/time=(\d+:\d+:\d+\.\d+)/);
    if (match) {
      const time = match[1];
      // 估算进度百分比
      const totalSeconds = parseTimeToSeconds(time);
      const progress = Math.min(Math.round(totalSeconds / 600 * 100), 100); // 假设视频时长约10分钟
      onProgress(progress);
    }
  });
  
  ffmpeg.on('close', (code) => {
    if (code !== 0) {
      return callback(new Error(`FFmpeg进程退出,代码: ${code}`));
    }
    callback(null, outputPath);
  });
};

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    error: '服务器内部错误',
    message: err.message,
    stack: process.env.NODE_ENV === 'development' ? err.stack : undefined
  });
});

部署与优化建议

在实际部署视频处理服务时,需要考虑性能优化和资源管理:

  1. 使用队列系统:将视频处理任务加入队列,避免长时间请求阻塞,可以使用Bull等队列库实现。

  2. 水平扩展:将视频处理服务与Web服务分离,独立部署转码服务器,通过消息队列实现任务分发。

  3. 存储优化:对于大量视频文件,建议使用对象存储服务,如AWS S3或阿里云OSS。

  4. 缓存策略:对热门视频进行缓存,减少重复转码,提高响应速度。

  5. 资源限制:限制同时处理的视频数量,避免服务器资源耗尽。

通过本文介绍的方法,你可以构建一个功能完善的视频处理系统。结合Express.js的灵活性和FFmpeg的强大功能,无论是简单的视频格式转换还是复杂的视频处理流程,都能轻松应对。如果你想进一步优化,可以研究分布式转码、GPU加速等高级技术,不断提升系统性能和用户体验。

参考项目中的下载示例可以帮助你实现视频文件的下载功能,将处理完成的视频提供给用户访问。同时,错误处理示例展示了如何优雅地处理各类异常情况,确保系统稳定运行。

希望本文对你构建视频处理系统有所帮助,如有任何问题,欢迎查阅Express.js官方文档或项目中的示例代码获取更多灵感。

【免费下载链接】express 【免费下载链接】express 项目地址: https://gitcode.com/gh_mirrors/exp/express

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐