---
url: /blog/kju9eeh3/index.md
---
# 商品视频封面处理功能开发规范

## 概述

本文档规定了在 ShopXO 系统中商品视频封面自动生成功能的开发流程、环境配置和使用规范。

## 功能说明

视频封面处理功能自动从商品视频中提取智能封面，通过亮度检测算法避免黑屏封面问题，提升商品展示效果。

### 核心特性

* **智能封面提取**: 自动扫描视频前N秒，跳过黑屏画面
* **亮度检测算法**: 使用 ITU-R BT.601 标准加权计算亮度 (Y = 0.299*R + 0.587*G + 0.114\*B)
* **批量处理**: 支持命令行批量处理已有商品视频
* **附件记录管理**: 自动将生成的封面记录到附件表

## 技术架构

```
Service 层: app/service/VideoCoverService.php
├── ExtractCover()          # 单个视频封面提取
├── BatchExtractCover()     # 批量处理
├── CalculateBrightness()   # 亮度计算
└── SaveAttachmentRecord()  # 附件记录保存

Command 层: app/command/VideoCover.php
├── video:cover             # 处理所有商品
└── video:cover --goods_id=N # 处理指定商品

配置文件: config/ueditor.php
└── ffmpeg 配置段

数据存储:
├── sxo_goods              # 商品表 (video 字段)
└── sxo_goods_videos       # 商品视频表 (cover 字段)
```

## 环境依赖

### 1. PHP 扩展要求

| 扩展名 | 用途 | 是否必需 |
|--------|------|----------|
| GD 库 | 图像处理（亮度计算、缩放） | 是 |
| json | JSON 数据处理 | 是 |
| mbstring | 多字节字符串处理 | 是 |

**检查 GD 库**:

```bash
php -m  //查看该命令下是否有gd库
```

### 2. Composer 依赖

在 [`composer.json`](composer.json) 中已包含：

```json
{
    "require": {
        "php-ffmpeg/php-ffmpeg": "^1.3"
    }
}
```

**安装依赖**:

```bash
composer install
```

### 3. FFmpeg 可执行文件

FFmpeg 是视频处理的核心工具，需要单独安装。

***

## Windows 环境配置

### FFmpeg 安装

#### 方式一：使用 Chocolatey（推荐）

```powershell
# 安装 Chocolatey（如果未安装）
Set-ExecutionPolicy Bypass -Scope Process -Force
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

# 安装 FFmpeg
choco install ffmpeg
```

#### 方式二：手动下载

1. 访问 <https://www.gyan.dev/ffmpeg/builds/>
2. 下载 `ffmpeg-release-essentials.zip`
3. 解压到目录，如 `C:\ffmpeg`
4. 添加到系统 PATH 环境变量：`C:\ffmpeg\bin`

**验证安装**:

```cmd
ffmpeg -version
ffprobe -version
```

### 配置文件修改

编辑 [`config/ueditor.php`](config/ueditor.php):

```php
'ffmpeg' => [
    // Windows: FFmpeg 安装目录的 bin 路径
    'bin_path' => 'C:\\ffmpeg\\bin\\',
    // 或使用绝对路径
    // 'bin_path' => 'C:\\ffmpeg\\bin\\ffmpeg.exe',

    'timeout' => 3600,
    'cover_width' => 150,
    'brightness_threshold' => 20,
    'max_scan_duration' => 10,
    'cover_save_dir' => 'upload/video/goods/',
],
```

**注意事项**:

* 路径中的反斜杠需要转义：`C:\\ffmpeg\\bin\\`
* 或使用单引号字符串：`'C:/ffmpeg/bin/'`
* 如果已添加到 PATH，可留空让系统自动查找

***

## Linux 环境配置

### FFmpeg 安装

#### Ubuntu/Debian

```bash
# 更新包列表
sudo apt update

# 安装 FFmpeg
sudo apt install ffmpeg

# 验证安装
ffmpeg -version
ffprobe -version
```

#### CentOS/RHEL/Rocky

```bash
# CentOS 7
sudo yum install epel-release
sudo yum install ffmpeg ffmpeg-devel

# CentOS 8 / Rocky Linux / AlmaLinux
sudo dnf install epel-release
sudo dnf install ffmpeg ffmpeg-devel

# 验证安装
ffmpeg -version
```

### 配置文件修改

编辑 [`config/ueditor.php`](config/ueditor.php):

```php
'ffmpeg' => [
    // Linux: FFmpeg 安装目录（通常是 /usr/bin/）
    'bin_path' => '/usr/bin/',
    // 或留空，通过 PATH 环境变量查找
    // 'bin_path' => '',

    'timeout' => 3600,
    'cover_width' => 150,
    'brightness_threshold' => 20,
    'max_scan_duration' => 10,
    'cover_save_dir' => 'upload/video/goods/',
],
```

**验证路径**:

```bash
which ffmpeg
# 输出: /usr/bin/ffmpeg
```

***

## 平台差异对比

| 配置项 | Windows | Linux |
|--------|---------|-------|
| **可执行文件** | `ffmpeg.exe`, `ffprobe.exe` | `ffmpeg`, `ffprobe` |
| **默认路径** | `C:\ffmpeg\bin\` | `/usr/bin/` |
| **路径分隔符** | `;` (PATH 环境变量) | `:` (PATH 环境变量) |
| **目录分隔符** | `\` (需转义为 `\\`) | `/` |
| **权限** | 需读取权限 | 需执行权限 (`chmod +x`) |
| **配置示例** | `'C:\\ffmpeg\\bin\\'` 或 `''` | `'/usr/bin/'` 或 `''` |

> \[!CAUTION]
>
> 注意，在代码中涉及 linux 目录下文件写入操作，确保赋予写权限。

**自动检测逻辑**:

系统在 [`VideoCoverService.php`](app/service/VideoCoverService.php:348) 中实现了自动路径检测：

```php
private static function DetectFFmpegPath($configuredPath, $command)
{
    $isWindows = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
    $executable = $isWindows ? $command . '.exe' : $command;

    if (empty($configuredPath)) {
        return $executable; // 依赖 PATH 环境变量
    }

    if (is_dir($configuredPath)) {
        $separator = $isWindows ? '\\' : '/';
        return rtrim($configuredPath, '/\\') . $separator . $executable;
    }

    return $configuredPath;
}
```

***

## 配置参数说明

在 [`config/ueditor.php`](config/ueditor.php:203) 中的 FFmpeg 配置段：

| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `bin_path` | string | `/usr/bin/` | FFmpeg 可执行文件目录 |
| `timeout` | int | `3600` | 处理超时时间（秒） |
| `cover_width` | int | `150` | 封面缩放宽度（用于亮度检测） |
| `brightness_threshold` | int | `20` | 黑屏判定阈值（0-255） |
| `max_scan_duration` | int | `10` | 最大扫描时长（秒） |
| `cover_save_dir` | string | `upload/video/goods/` | 封面存储目录 |

***

## 使用方式

### 1. 命令行批量处理

```bash
# 处理所有视频列表（goods_videos 表中 cover 为空的记录）
php think video:cover

# 处理指定商品的视频
php think video:cover --goods_id=123

# 处理推荐视频（goods 表中 video 字段）
php think video:cover --type=2
```

### 2. 代码调用

```php
use app\service\VideoCoverService;

// 单个视频处理
$videoPath = 'D:/project/public/static/upload/video/goods/2025/01/31/video.mp4';
$result = VideoCoverService::ExtractCover($videoPath);

if ($result['success']) {
    $coverPath = $result['data'];
    echo "封面生成成功: {$coverPath}";
} else {
    echo "错误: {$result['msg']}";
}

// 批量处理
$videoPaths = [
    '/path/to/video1.mp4',
    '/path/to/video2.mp4',
];
$batchResult = VideoCoverService::BatchExtractCover($videoPaths);
```

***

## 核心算法说明

### 亮度计算算法

使用 ITU-R BT.601 标准的加权平均法：

```
Y = 0.299 × R + 0.587 × G + 0.114 × B
```

* **Y**: 亮度值 (0-255)
* **R/G/B**: 红/绿/蓝分量 (0-255)
* **阈值**: 默认 20，低于此值视为黑屏

### 封面提取流程

```
1. 获取视频时长
2. 从第0秒开始，每隔1秒提取一帧
3. 计算帧画面平均亮度
4. 如果亮度 > 阈值，保存为封面
5. 如果扫描完仍无有效画面，使用第1秒作为兜底
6. 保存封面文件和附件记录
```

***

## 封面文件命名规则

封面文件保存在与视频相同的目录下：

```
视频路径: static/upload/video/goods/2025/01/31/1234567890.mp4
封面路径: static/upload/video/goods/2025/01/31/video_cover_1234567890.jpg
```

命名格式: `video_cover_{视频文件名}.jpg`

***

## 数据库结构

### sxo\_goods 表

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | int | 商品ID |
| `video` | varchar(255) | 推荐视频路径 |

### sxo\_goods\_videos 表

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | int | 视频ID |
| `goods_id` | int | 商品ID |
| `video` | varchar(255) | 视频文件路径 |
| `cover` | varchar(255) | 封面文件路径 |
| `is_show` | tinyint | 是否显示 |

### sxo\_attachment 表

封面生成后自动记录到附件表：

| 字段 | 类型 | 说明 |
|------|------|------|
| `id` | int | 附件ID |
| `title` | varchar(255) | 文件名 (video\_cover\_xxx) |
| `url` | varchar(255) | 相对路径 |
| `type` | varchar(20) | 类型 (image) |
| `ext` | varchar(10) | 扩展名 (jpg) |
| `size` | int | 文件大小（字节） |
| `hash` | varchar(32) | MD5 哈希值 |

***

## 故障排查

### 常见问题

#### 1. FFmpeg 未找到

**错误信息**:

```
Failed to execute FFmpeg: sh: ffmpeg: not found
```

**解决方案**:

* Linux: `sudo apt install ffmpeg`
* Windows: 安装 FFmpeg 并添加到 PATH
* 或在配置文件中指定完整路径

#### 2. GD 库未安装

**错误信息**:

```
GD库未安装或不支持JPEG
```

**解决方案**:

```bash
# Ubuntu/Debian
sudo apt install php-gd

# CentOS/RHEL
sudo yum install php-gd

# Windows
取消注释 php.ini 中的: extension=gd
```

#### 3. 权限问题（Linux）

**错误信息**:

```
Permission denied: /path/to/cover.jpg
```

**解决方案**:

```bash
# 确保上传目录可写
sudo chmod -R 755 public/static/upload/
sudo chown -R www-data:www-data public/static/upload/
```

#### 4. 处理超时

**错误信息**:

```
FFmpeg process timeout exceeded
```

**解决方案**:
在 [`config/ueditor.php`](config/ueditor.php) 中增加超时时间：

```php
'ffmpeg' => [
    'timeout' => 7200, // 增加到2小时
],
```

***

## 相关文件清单

| 文件路径 | 类型 | 说明 |
|---------|------|------|
| [`app/service/VideoCoverService.php`](app/service/VideoCoverService.php) | Service | 视频封面服务 |
| [`app/command/VideoCover.php`](app/command/VideoCover.php) | Command | 命令行工具 |
| [`config/ueditor.php`](config/ueditor.php) | Config | FFmpeg 配置 |
| [`composer.json`](composer.json) | Config | Composer 依赖 |
| `app/admin/controller/Goods.php` | Controller | 商品管理后台 |
| `app/api/controller/Goods.php` | Controller | 商品 API |
