# FFmpeg 工具模块

基于 FFmpeg/FFprobe 的 Node.js 视频处理工具集，提供视频信息获取、缩略图生成、视频截取、音频提取及运行环境检查等功能。

本模块由 `@xiping/node-utils` 包统一导出，使用时从包根路径导入即可：`import { ... } from '@xiping/node-utils'`。

## 功能概览

| 功能       | 说明                     | 依赖        |
|------------|--------------------------|-------------|
| 视频信息   | 时长、分辨率、编码、比特率等 | ffprobe    |
| 缩略图生成 | 多帧合成预览图（AVIF/WebP/JPEG/PNG） | ffmpeg + sharp |
| 视频截取   | 按时间范围截取，流复制不重编码 | ffmpeg     |
| 提取音频   | 从视频中提取音轨为 mp3/m4a/wav | ffmpeg     |
| 视频转码   | HEVC/AV1 转码，支持转换进度回调 | ffmpeg     |
| 环境检查   | 检测 ffmpeg/ffprobe 是否可用 | -          |

## 安装要求

### 依赖包

- Node.js（使用内置 `child_process.spawnSync` 调用 ffmpeg/ffprobe，兼容 Electron、跨平台）
- [sharp](https://www.npmjs.com/package/sharp)（仅缩略图功能需要）

### 系统要求

- **ffprobe**：视频信息相关接口需要（通常随 ffmpeg 一起安装）
- **ffmpeg**：缩略图、视频截取需要

### 安装 FFmpeg / FFprobe

**macOS:**

```bash
brew install ffmpeg
```

**Ubuntu/Debian:**

```bash
sudo apt update
sudo apt install ffmpeg
```

**CentOS/RHEL:**

```bash
sudo yum install ffmpeg
```

**Windows:**

从 [FFmpeg 官网](https://ffmpeg.org/download.html) 下载并将可执行文件加入 PATH，或使用 Chocolatey：

```bash
choco install ffmpeg
```

---

## 1. 视频信息（getVideoInfo）

使用 ffprobe 获取视频元数据：时长、分辨率、编码、比特率、帧率、文件大小等。

### 基本使用

```typescript
import { getVideoInfo } from '@xiping/node-utils';

const videoInfo = getVideoInfo('/path/to/video.mp4');
console.log(videoInfo.durationFormatted, videoInfo.width, videoInfo.height);
```

### 检查 ffprobe 是否可用

```typescript
import { isFfprobeAvailable } from '@xiping/node-utils';

if (isFfprobeAvailable()) {
  console.log('ffprobe 可用');
}
```

### 批量获取

```typescript
import { getMultipleVideoInfo } from '@xiping/node-utils';

const infos = getMultipleVideoInfo(['/path/video1.mp4', '/path/video2.mp4']);
```

### 获取详细元数据（含章节等）

```typescript
import { getDetailedVideoInfo } from '@xiping/node-utils';

const detailed = getDetailedVideoInfo('/path/to/video.mp4');
```

### VideoInfo 结构

```typescript
interface VideoInfo {
  path: string;
  duration: number;           // 秒
  durationFormatted: string;
  width: number;
  height: number;
  videoCodec: string;
  audioCodec: string;
  bitrate: number;
  fps: number;
  fileSize: number;           // 字节
  fileSizeFormatted: string;
  rawInfo: string;            // 原始 ffprobe JSON
}
```

---

## 2. 缩略图生成（getThumbnail）

从视频中等间隔提取多帧，用 sharp 合成为一张缩略图，支持 AVIF/WebP/JPEG/PNG。

### 基本使用

```typescript
import { getThumbnail } from '@xiping/node-utils';

const result = await getThumbnail('/path/to/video.mp4', {
  frames: 60,           // 提取帧数（默认 60）
  columns: 4,           // 每行列数（默认 4）
  outputWidth: 3840,    // 输出宽度（默认 3840）
  format: 'avif',       // 'avif' | 'webp' | 'jpeg' | 'png'
  quality: 80,          // 1–100
  outputFileName: 'thumbnail.avif',
});

console.log(result.outputPath);
console.log(result.metadata);
```

### 命令行（`video-thumbnail`）

安装 `@xiping/node-utils` 后可用 `video-thumbnail`，与上方 API 选项对应（见 `--help`）。

**未在项目中安装依赖、临时在一台机器上执行：**

```bash
npx -p @xiping/node-utils@latest video-thumbnail --input /path/to/video.mp4
```

（包名是 `@xiping/node-utils`，命令名是 `video-thumbnail`，临时拉包时需用 `-p` 指定包名。）

**已全局安装**（之后可省略 `npx` 与 `-p`）：

```bash
npm i -g @xiping/node-utils
video-thumbnail --input /path/to/video.mp4
```

**当前项目已依赖本包** 时，可直接 `npx video-thumbnail --input ...`，无需 `-p`。

### 进度回调

```typescript
const result = await getThumbnail('/path/to/video.mp4', {
  frames: 30,
  onProgress(progress) {
    console.log(progress.phase, progress.percent, progress.message);
    // phase: 'analyzing' | 'extracting' | 'composing' | 'encoding' | 'done'
  },
});
```

### 选项说明

| 选项            | 类型     | 默认值         | 说明           |
|-----------------|----------|----------------|----------------|
| frames          | number   | 60             | 提取的帧数     |
| columns         | number   | 4              | 缩略图列数     |
| outputWidth     | number   | 3840           | 输出图宽度     |
| outputFileName  | string   | thumbnail.avif | 输出文件名     |
| format          | string   | 'avif'         | avif/webp/jpeg/png |
| quality         | number   | 80             | 1–100          |
| batchSize       | number   | 10             | 合成时每批帧数 |
| maxConcurrency  | number   | 4              | 提取帧并发数   |
| tempDir         | string   | -              | 自定义临时目录 |
| onProgress      | function | -              | 进度回调       |

### 返回值

```typescript
{
  buffer: Buffer;
  outputPath: string;
  metadata: {
    frames: number;
    duration: number;
    outputSize: { width: number; height: number };
  };
}
```

**注意**：输入路径需为**绝对路径**；使用前请确保已安装 ffmpeg（可用下方「环境检查」接口检测）。

---

## 3. 视频截取（cutVideo）

按开始时间、时长或结束时间截取片段，使用流复制（`-c copy`），不重新编码。

### 基本使用

```typescript
import { cutVideo, cutVideoByTimeRange, cutVideoByDuration, cutVideoFromStart } from '@xiping/node-utils';

// 从 30 秒开始截取 60 秒
const result = await cutVideo('/path/to/video.mp4', {
  startTime: '00:00:30',
  duration: '00:01:00',
  outputFormat: 'mp4',
  overwrite: true,
});

// 按时间范围：1:30 到 3:45
const r2 = await cutVideoByTimeRange('/path/to/video.mp4', '00:01:30', '00:03:45');

// 从 2 分钟开始截取 90 秒
const r3 = await cutVideoByDuration('/path/to/video.mp4', '00:02:00', '00:01:30');

// 只取前 30 秒
const r4 = await cutVideoFromStart('/path/to/video.mp4', 30);
```

### CutVideoOptions

```typescript
{
  startTime?: string;      // 开始时间，如 '00:00:30' 或秒数
  duration?: string;       // 持续时间
  endTime?: string;        // 结束时间（与 duration 二选一）
  outputFileName?: string;
  outputFormat?: string;   // 默认 'mp4'
  tempDir?: string;
  overwrite?: boolean;      // 是否覆盖已存在文件
}
```

### CutVideoResult

```typescript
{
  outputPath: string;
  metadata: {
    originalDuration: number;
    cutDuration: number;
    startTime: string;
    endTime: string;
    fileSize: number;
    processingTime: number;
  };
}
```

时间格式支持：`HH:MM:SS`、`MM:SS` 或纯秒数。输入路径需为**绝对路径**。更多说明见 [README_cutVideo.md](./README_cutVideo.md)。

---

## 4. 提取音频（extractAudio）

从视频文件中仅提取音频轨，输出为独立音频文件（mp3、m4a、wav）。

### 基本使用

```typescript
import { extractAudio } from '@xiping/node-utils';

const result = await extractAudio('/path/to/video.mp4', {
  outputFormat: 'mp3',   // 'mp3' | 'm4a' | 'wav'，默认 'mp3'
  outputFileName: 'audio.mp3',
  overwrite: true,
});

console.log(result.outputPath);
console.log(result.metadata.duration, result.metadata.fileSize);
```

### 进度回调

```typescript
const result = await extractAudio('/path/to/video.mp4', {
  outputFormat: 'mp3',
  onProgress(progress) {
    console.log(progress.phase, progress.percent, progress.message);
    // phase: 'preparing' | 'encoding' | 'done'
    if (progress.currentTime != null && progress.duration != null) {
      console.log(`已处理 ${progress.currentTime}/${progress.duration} 秒`);
    }
  },
});
```

### 选项说明

| 选项            | 类型     | 默认值   | 说明                         |
|-----------------|----------|----------|------------------------------|
| outputFileName  | string   | 源文件名.格式 | 输出文件名                   |
| outputFormat    | string   | 'mp3'    | mp3 / m4a / wav              |
| overwrite       | boolean  | false    | 是否覆盖已存在文件           |
| tempDir         | string   | -        | 自定义临时目录               |
| copyStream      | boolean  | true     | 尽量流复制不重编码（格式兼容时） |
| onProgress      | function | -        | 进度回调                     |

### 返回值

```typescript
{
  outputPath: string;
  metadata: {
    duration: number;      // 音频时长（秒）
    fileSize: number;      // 输出文件大小（字节）
    processingTime: number; // 处理耗时（毫秒）
  };
}
```

输入路径需为**绝对路径**；若视频无音轨将抛错。使用前请确保已安装 ffmpeg。

---

## 5. 视频转码（transcoding）

将视频转码为 HEVC 或 AV1，不依赖 shelljs，仅使用 Node 内置 `child_process`（spawn）与 `fs`。默认生成新文件（如 `{原名}_hevc.mp4` 或 `{原名}_av1.mp4`），不替换原文件；可选开启“替换原文件”。支持转换进度回调。

### 基本使用

```typescript
import { transcoding, getAvailableEncoders } from '@xiping/node-utils';

// 转码为 HEVC（默认），生成新文件
const result = await transcoding('/path/to/video.mp4');
console.log(result.outputPath); // 如 /path/to/video_hevc.mp4

// 指定 AV1、不生成缩略图
const r2 = await transcoding('/path/to/video.mp4', {
  format: 'av1',
  generateThumbnail: false,
});

// 替换原文件（删除原文件并将输出重命名为原路径）
const r3 = await transcoding('/path/to/video.mp4', {
  format: 'hevc',
  replaceOriginal: true,
});
```

### 命令行（`video-transcoding`）

安装 `@xiping/node-utils` 后可使用 `video-transcoding`（参数见 `--help`）。

**未在项目中安装依赖、临时执行：**

```bash
npx -p @xiping/node-utils@latest video-transcoding --input /path/to/video.mp4
```

**已全局安装：**

```bash
npm i -g @xiping/node-utils
video-transcoding --input /path/to/video.mp4
```

常用示例：

```bash
# 转码为 AV1
video-transcoding --input /path/to/video.mp4 --format av1

# 强制软件编码并替换原文件
video-transcoding --input /path/to/video.mp4 --force-software --replace-original

# 仅转码，不生成缩略图
video-transcoding --input /path/to/video.mp4 --no-thumbnail
```

选项：

| 选项                | 说明 |
|---------------------|------|
| `-i, --input <path>` | 输入视频路径（必填） |
| `--format <fmt>`    | `hevc` \| `av1`（默认 `hevc`） |
| `--force-software`  | 强制使用软编码 |
| `--replace-original`| 替换原文件为转码结果 |
| `--no-thumbnail`    | 不生成缩略图 |
| `-h, --help`        | 显示帮助 |

### 转换进度（onProgress）

```typescript
const result = await transcoding('/path/to/video.mp4', {
  onProgress(progress) {
    console.log(progress.phase, progress.percent, progress.message);
    // phase: 'encoding' | 'done'
    if (progress.currentTime != null && progress.duration != null) {
      console.log(`已处理 ${progress.currentTime}/${progress.duration} 秒`);
    }
  },
});
```

### TranscodingConfig

| 选项               | 类型     | 默认值   | 说明 |
|--------------------|----------|----------|------|
| format             | string   | 'hevc'   | 目标编码：'hevc' \| 'av1' |
| generateThumbnail  | boolean  | true     | 是否生成缩略图 |
| replaceOriginal    | boolean  | false    | 是否替换原文件（删除原文件并将输出重命名为原路径） |
| onProgress         | function | -        | 转换进度回调 |

### TranscodeProgress（onProgress 回调参数）

| 字段        | 类型   | 说明 |
|-------------|--------|------|
| phase       | string | 阶段：'encoding' \| 'done' |
| percent     | number | 总进度 0–100 |
| message     | string | 可读描述 |
| currentTime | number | 当前已处理时长（秒） |
| duration    | number | 视频总时长（秒） |

### 编码器检测

```typescript
import { getAvailableEncoders } from '@xiping/node-utils';

const encoders = getAvailableEncoders();
// { av1Nvenc, libaomAv1, hevcNvenc, libx265 }
```

HEVC 优先使用 hevc_nvenc（NVIDIA），否则使用 libx265；AV1 优先使用 av1_nvenc，否则使用 libaom-av1。输入路径需为**绝对路径**。

---

## 6. 环境检查（check）

```typescript
import { checkFFmpegAvailability, isFfprobeAvailable } from '@xiping/node-utils';

if (checkFFmpegAvailability()) {
  console.log('ffmpeg 可用');
}
if (isFfprobeAvailable()) {
  console.log('ffprobe 可用');
}
```

- **checkFFmpegAvailability()**：用于缩略图、视频截取、提取音频、视频转码前检查。
- **isFfprobeAvailable()**：用于视频信息接口前检查。

---

## 错误处理

各函数在以下情况会抛出错误，建议用 try/catch 包裹：

- 文件不存在或路径无效
- 未安装 ffmpeg/ffprobe 或不可用
- 格式/参数不支持（如时间超出视频时长、视频无音轨）
- 权限或磁盘空间不足

```typescript
try {
  const info = getVideoInfo('/path/to/video.mp4');
} catch (err) {
  console.error(err.message);
}
```

---

## 支持格式

ffprobe/ffmpeg 支持常见容器与编码，例如：

- 容器：MP4, AVI, MOV, MKV, FLV, WMV, WebM, OGV, 3GP 等
- 具体支持以系统安装的 FFmpeg 版本为准

---

## API 速览

| 接口                     | 说明 |
|--------------------------|------|
| `getVideoInfo(path)`     | 获取视频基本信息 |
| `getMultipleVideoInfo(paths)` | 批量获取视频信息 |
| `getDetailedVideoInfo(path)`  | 获取详细元数据（含章节等） |
| `getThumbnail(path, options)` | 生成多帧缩略图（异步） |
| `cutVideo(path, options)`     | 按选项截取视频（异步） |
| `cutVideoByTimeRange(path, start, end, options)` | 按起止时间截取 |
| `cutVideoByDuration(path, start, duration, options)` | 按起始+时长截取 |
| `cutVideoFromStart(path, durationSeconds, options)`   | 从开头截取 N 秒 |
| `extractAudio(path, options)` | 从视频提取音频（异步） |
| `transcoding(path, options)` | 视频转码为 HEVC/AV1（异步，支持进度） |
| `getAvailableEncoders()`      | 检测可用编码器（av1_nvenc、libaom-av1、hevc_nvenc、libx265） |
| `isFfprobeAvailable()`   | 检测 ffprobe 是否可用 |
| `checkFFmpegAvailability()` | 检测 ffmpeg 是否可用 |
