winupdate-neo/lib/encodeVideo.ts

119 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// file: compressImages.ts
import ffmpeg from 'fluent-ffmpeg';
import { Buffer } from 'buffer';
import * as fs from 'fs/promises';
import * as path from 'path';
import * as os from 'os';
import { Readable } from 'stream';
export interface ImageInput {
buffer: Buffer;
contentType: string;
}
export interface Av1CompressOptions {
framerate?: number;
crf?: number;
preset?: number;
pixFmt?: string;
}
const mimeToExtensionMap: Record<string, string> = {
'image/png': 'png',
'image/jpeg': 'jpg',
'image/jpg': 'jpg',
'image/webp': 'webp',
'image/bmp': 'bmp',
};
/**
* 将一个包含不同类型图片的Buffer数组压缩成一个使用SVT-AV1编码的视频Buffer。
* (已修复顺序问题和视频截断问题)
*
* @param images 包含图片对象buffer 和 contentType的数组。
* @param options 压缩选项。
* @returns {Promise<Buffer>} 一个Promise解析后为包含视频数据的Buffer。
*/
export async function compressImagesToAv1Video(
images: ImageInput[],
options: Av1CompressOptions = {}
): Promise<Buffer> {
if (!images || images.length === 0) {
throw new Error('Input image array cannot be empty.');
}
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'ffmpeg-av1-'));
try {
const {
framerate = 25,
crf = 30,
preset = 8,
pixFmt = 'yuv420p',
} = options;
// --- 修复点 1: 修正写入和 list.txt 生成的逻辑 ---
// 1. 先生成所有文件名,并并行写入所有文件
const fileNames: string[] = [];
const writePromises = images.map(async (image, index) => {
const extension = mimeToExtensionMap[image.contentType.toLowerCase()];
if (!extension) {
throw new Error(`Unsupported content type: ${image.contentType}`);
}
const frameNumber = (index + 1).toString().padStart(5, '0');
const fileName = `frame-${frameNumber}.${extension}`;
fileNames[index] = fileName; // 按正确顺序填充文件名数组
const filePath = path.join(tempDir, fileName);
return fs.writeFile(filePath, image.buffer);
});
await Promise.all(writePromises);
// 2. 所有文件写入成功后,再生成 concat 文件内容
const concatFileContent = fileNames.map(name => `file '${name}'`).join('\n');
const concatFilePath = path.join(tempDir, 'list.txt');
await fs.writeFile(concatFilePath, concatFileContent);
// --- 修复点 2: 修正 FFmpeg 命令选项 ---
return new Promise<Buffer>((resolve, reject) => {
const command = ffmpeg(concatFilePath)
.inputOptions([
'-f', 'concat',
'-safe', '0',
`-r ${framerate}`, // 关键:将帧率作为输入选项
])
.videoCodec('libsvtav1')
.outputOptions([
`-crf ${crf}`,
`-preset ${preset}`,
'-movflags frag_keyframe+empty_moov',
])
.toFormat('mp4');
// 添加一个start监听器方便调试它会打印出最终执行的命令
command.on('start', (commandLine) => {
console.log('Spawning FFmpeg with command: ' + commandLine);
});
const outputStream = command.pipe() as Readable;
const chunks: Buffer[] = [];
outputStream.on('data', (chunk) => chunks.push(chunk));
outputStream.on('error', reject);
outputStream.on('end', () => resolve(Buffer.concat(chunks)));
command.on('error', (err, stdout, stderr) => {
reject(new Error(`FFmpeg error: ${err.message}\nStderr: ${stderr}`));
});
}).finally(() => {
//fs.rm(tempDir, { recursive: true, force: true });
});
} catch (error) {
//await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
throw error;
}
}
// ... 示例代码 main() 函数无需修改,可以正常工作