当前位置:首页 > 技术 > 正文内容

基于 FFmpeg 与 Nuxt 3 实现服务端图片压缩与视频截帧

访客 技术 2026年8月9日 1

业务场景与技术方案

在Web应用开发中,多媒体资源的优化是提升用户体验的关键环节。常见的业务需求包括:提取视频关键帧作为列表封面,以及对高清原图进行压缩以生成缩略图。虽然各大云存储服务商提供了相应的图片处理和视频截帧API,但这通常会产生额外的计费。本文将探讨如何利用开源工具 FFmpeg 结合 Nuxt 3 框架,在自建服务端低成本地实现上述功能。

FFmpeg 核心命令解析

FFmpeg 是一款强大的开源音视频处理工具。针对本文的场景,我们主要使用以下两个核心命令:

1. 图片等比例缩放

ffmpeg -i input.jpg -vf scale=300:-1 output.jpg

该命令使用 scale 视频滤镜。将宽度固定为 300 像素,高度参数设置为 -1 表示 FFmpeg 会自动计算高度以保持原始宽高比。

2. 视频指定时间截帧

ffmpeg -i input.mp4 -ss 00:00:03 -frames:v 1 frame.jpg

-ss 参数用于定位时间戳(此处为第3秒),-frames:v 1 表示仅提取一帧视频画面并保存为图片。

Nuxt 3 项目实战

我们将使用 Nuxt 3 构建一个全栈应用,前端负责文件上传,后端调用 FFmpeg 进行异步处理。

初始化项目

npx nuxi@latest init media-processor
cd media-processor
yarn install

前端页面实现

pages/index.vue 中构建文件上传组件,并使用 Nuxt 3 的 $fetch 提交数据。

<template>
  <div class="upload-container">
    <input type="file" @change="handleUpload" accept="image/*,video/*" />
  </div>
</template>

<script setup lang="ts">
const handleUpload = async (event: Event) => {
  const target = event.target as HTMLInputElement;
  const selectedFile = target.files?.[0];
  if (!selectedFile) return;

  const payload = new FormData();
  payload.append('media', selectedFile);

  const response = await $fetch('/api/process-media', {
    method: 'POST',
    body: payload
  });
  console.log('处理结果:', response);
};
</script>

后端 API 与 FFmpeg 集成

server/api/process-media.post.ts 中处理上传逻辑。这里我们摒弃了传统的第三方解析库,直接使用 Nuxt 3 内置的 readMultipartFormData,并通过 util.promisify 将 FFmpeg 命令异步化,避免阻塞 Node.js 事件循环。

import { exec } from 'child_process';
import { promisify } from 'util';
import { writeFile, mkdir } from 'fs/promises';
import { join } from 'path';

const execAsync = promisify(exec);
const UPLOAD_DIR = join(process.cwd(), 'public', 'uploads');

export default defineEventHandler(async (event) => {
  // 确保上传目录存在
  await mkdir(UPLOAD_DIR, { recursive: true });

  const formData = await readMultipartFormData(event);
  if (!formData) throw createError({ statusCode: 400, message: '未接收到文件' });

  const filePart = formData.find(part => part.name === 'media');
  if (!filePart) throw createError({ statusCode: 400, message: '缺少 media 字段' });

  const originalName = filePart.filename || 'unknown';
  const ext = originalName.split('.').pop();
  const baseName = originalName.replace(`.${ext}`, '');
  const tempPath = join(UPLOAD_DIR, `temp_${Date.now()}.${ext}`);

  // 将内存中的文件流写入临时文件
  await writeFile(tempPath, filePart.data);

  let resultMessage = '';

  if (filePart.type?.startsWith('image/')) {
    // 图片压缩:限制高度为 200,宽度等比例缩放
    const outputPath = join(UPLOAD_DIR, `thumb_${baseName}.${ext}`);
    await execAsync(`ffmpeg -i "${tempPath}" -vf scale=-1:200 "${outputPath}" -y`);
    resultMessage = `图片已压缩,输出至: thumb_${baseName}.${ext}`;
  } else if (filePart.type?.startsWith('video/')) {
    // 视频截帧:截取第 2 秒的画面
    const outputPath = join(UPLOAD_DIR, `cover_${baseName}.jpg`);
    await execAsync(`ffmpeg -i "${tempPath}" -ss 00:00:02 -frames:v 1 "${outputPath}" -y`);
    resultMessage = `视频已截帧,输出至: cover_${baseName}.jpg`;
  } else {
    throw createError({ statusCode: 415, message: '不支持的文件类型' });
  }

  return { success: true, message: resultMessage };
});

PM2 进程管理配置

在项目根目录创建 ecosystem.config.js 以配置集群模式启动。

module.exports = {
  apps: [
    {
      name: 'media-server',
      port: '3000',
      exec_mode: 'cluster',
      instances: 2,
      script: './.output/server/index.mjs'
    }
  ]
};

Linux 服务端部署

将代码部署至 Ubuntu 服务器,首先需要安装必要的系统依赖和运行环境。

环境准备

# 安装 FFmpeg
sudo apt update
sudo apt install ffmpeg -y

# 验证 FFmpeg 安装
ffmpeg -version

# 使用 nvm 安装 Node.js (Nuxt 3 需要 Node 16.x+)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install 18
nvm use 18

# 安装 Yarn 和 PM2
npm install -g yarn pm2

构建与启动

拉取代码并安装依赖后,执行构建命令并通过 PM2 启动服务。

yarn install
yarn build
pm2 start ecosystem.config.js

服务启动后,访问服务器 IP 及对应端口(如 http://<server-ip>:3000),即可通过前端页面上传媒体文件。服务端将自动在 public/uploads 目录下生成带有 thumb_ 前缀的压缩图片,或带有 cover_ 前缀的视频封面帧。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。