"use strict";
/**
 * 视频生成模块 - FFmpeg 命令封装
 * 实现 Ken Burns 效果、图片+音频合成等功能
 */
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateVideo = generateVideo;
exports.generateVideoWithBgm = generateVideoWithBgm;
exports.generateSlideshow = generateSlideshow;
exports.getVideoInfo = getVideoInfo;
const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg"));
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const OUTPUT_DIR = path_1.default.join(process.cwd(), 'public', 'videos');
const TEMP_DIR = path_1.default.join(process.cwd(), 'temp');
// 确保目录存在
function ensureDirectories() {
    if (!fs_1.default.existsSync(OUTPUT_DIR))
        fs_1.default.mkdirSync(OUTPUT_DIR, { recursive: true });
    if (!fs_1.default.existsSync(TEMP_DIR))
        fs_1.default.mkdirSync(TEMP_DIR, { recursive: true });
}
/**
 * 合成图片+音频生成最终视频
 */
async function generateVideo(imagePath, audioPath, outputPath, config) {
    ensureDirectories();
    return new Promise((resolve, reject) => {
        const command = (0, fluent_ffmpeg_1.default)();
        command
            .input(imagePath)
            .inputOptions(['-loop', '1'])
            .input(audioPath);
        // 构建视频滤镜
        const videoFilters = [];
        // Ken Burns 效果
        if (config.kenburns?.enabled) {
            const kb = config.kenburns;
            videoFilters.push({
                filter: 'zoompan',
                options: {
                    z: `if(gte(zoom,${kb.minZoom}),min(zoom+0.001,${kb.maxZoom}),${kb.minZoom})`,
                    x: 'iw/2-(iw/zoom/2)',
                    y: 'ih/2-(ih/zoom/2)',
                    d: 1,
                    s: `${config.video.width}x${config.video.height}`,
                    fps: config.video.fps,
                },
            });
        }
        // 缩放和填充
        videoFilters.push({
            filter: 'scale',
            options: `${config.video.width}:${config.video.height}:force_original_aspect_ratio=decrease`,
        });
        videoFilters.push({
            filter: 'pad',
            options: `${config.video.width}:${config.video.height}:(ow-iw)/2:(oh-ih)/2:color=black`,
        });
        // 字幕
        if (config.subtitle) {
            const sub = config.subtitle;
            const yPos = sub.position === 'top' ? 20 : sub.position === 'center' ? (config.video.height - 40) / 2 : config.video.height - 60;
            videoFilters.push({
                filter: 'drawtext',
                options: {
                    text: sub.text,
                    fontsize: sub.fontSize || 24,
                    fontcolor: sub.fontColor || 'white',
                    borderw: 2,
                    bordercolor: 'black',
                    x: '(w-text_w)/2',
                    y: yPos,
                },
            });
        }
        command.videoFilters(videoFilters);
        command
            .outputOptions([`-af volume=${config.audio.volume}`])
            .outputOptions([
            '-c:v libx264',
            '-preset fast',
            '-crf 18',
            '-c:a aac',
            '-b:a 192k',
            '-pix_fmt yuv420p',
            '-shortest',
        ])
            .output(outputPath);
        command.on('progress', (progress) => {
            if (progress.percent)
                console.log(`Processing: ${progress.percent.toFixed(1)}% done`);
        });
        command.on('end', () => {
            fluent_ffmpeg_1.default.ffprobe(outputPath, (err, metadata) => {
                if (err) {
                    reject(err);
                    return;
                }
                const stats = fs_1.default.statSync(outputPath);
                resolve({
                    duration: Math.round(metadata.format.duration || 0),
                    fileSize: stats.size,
                });
            });
        });
        command.on('error', reject);
        command.run();
    });
}
/**
 * 合成带背景音乐的混音视频
 */
async function generateVideoWithBgm(imagePath, audioPath, bgmPath, outputPath, config) {
    ensureDirectories();
    return new Promise((resolve, reject) => {
        const command = (0, fluent_ffmpeg_1.default)();
        command
            .input(imagePath)
            .inputOptions(['-loop 1'])
            .input(audioPath)
            .input(bgmPath)
            .inputOptions(['-stream_loop -1']);
        const videoFilters = [];
        if (config.kenburns?.enabled) {
            const kb = config.kenburns;
            videoFilters.push({
                filter: 'zoompan',
                options: {
                    z: `if(gte(zoom,${kb.minZoom}),min(zoom+0.001,${kb.maxZoom}),${kb.minZoom})`,
                    x: 'iw/2-(iw/zoom/2)',
                    y: 'ih/2-(ih/zoom/2)',
                    d: 1,
                    s: `${config.video.width}x${config.video.height}`,
                    fps: config.video.fps,
                },
            });
        }
        videoFilters.push({
            filter: 'scale',
            options: `${config.video.width}:${config.video.height}:force_original_aspect_ratio=decrease`,
        });
        videoFilters.push({
            filter: 'pad',
            options: `${config.video.width}:${config.video.height}:(ow-iw)/2:(oh-ih)/2:color=black`,
        });
        command.videoFilters(videoFilters);
        // 音频混音
        const bgm = config.bgm;
        command.audioFilters(`[1:a]volume=${config.audio.volume}[main];[2:a]volume=${bgm.volume}[bgm];[main][bgm]amix=inputs=2:duration=longest[aout]`);
        command
            .outputOptions([
            '-tune stillimage',
            '-c:v libx264',
            '-preset fast',
            '-crf 18',
            '-c:a aac',
            '-b:a 192k',
            '-pix_fmt yuv420p',
            '-shortest',
            '-map 0:v',
            '-map [aout]',
        ])
            .output(outputPath);
        command.on('end', () => {
            fluent_ffmpeg_1.default.ffprobe(outputPath, (err, metadata) => {
                if (err) {
                    reject(err);
                    return;
                }
                const stats = fs_1.default.statSync(outputPath);
                resolve({ duration: Math.round(metadata.format.duration || 0), fileSize: stats.size });
            });
        });
        command.on('error', reject);
        command.run();
    });
}
/**
 * 生成多图轮播视频
 */
async function generateSlideshow(images, audioPath, outputPath, config) {
    ensureDirectories();
    const concatListPath = path_1.default.join(TEMP_DIR, `concat_${Date.now()}.txt`);
    const concatList = images.map((img) => `file '${img.path}'`).join('\n');
    fs_1.default.writeFileSync(concatListPath, concatList);
    return new Promise((resolve, reject) => {
        const command = (0, fluent_ffmpeg_1.default)();
        command
            .input(concatListPath)
            .inputOptions(['-f concat', '-safe 0'])
            .input(audioPath);
        const videoFilters = [];
        videoFilters.push({
            filter: 'scale',
            options: `${config.video.width}:${config.video.height}:force_original_aspect_ratio=decrease`,
        });
        videoFilters.push({
            filter: 'pad',
            options: `${config.video.width}:${config.video.height}:(ow-iw)/2:(oh-ih)/2:color=black`,
        });
        if (config.subtitle) {
            const sub = config.subtitle;
            const yPos = sub.position === 'top' ? 20 : sub.position === 'center' ? (config.video.height - 40) / 2 : config.video.height - 60;
            videoFilters.push({
                filter: 'drawtext',
                options: {
                    text: sub.text,
                    fontsize: sub.fontSize || 24,
                    fontcolor: sub.fontColor || 'white',
                    borderw: 2,
                    bordercolor: 'black',
                    x: '(w-text_w)/2',
                    y: yPos,
                },
            });
        }
        command.videoFilters(videoFilters);
        command
            .outputOptions([
            '-c:v libx264',
            '-preset fast',
            '-crf 18',
            '-c:a aac',
            '-b:a 192k',
            '-pix_fmt yuv420p',
            '-shortest',
        ])
            .output(outputPath);
        command.on('end', () => {
            fs_1.default.unlinkSync(concatListPath);
            fluent_ffmpeg_1.default.ffprobe(outputPath, (err, metadata) => {
                if (err) {
                    reject(err);
                    return;
                }
                const stats = fs_1.default.statSync(outputPath);
                resolve({ duration: Math.round(metadata.format.duration || 0), fileSize: stats.size });
            });
        });
        command.on('error', (err) => {
            fs_1.default.unlinkSync(concatListPath);
            reject(err);
        });
        command.run();
    });
}
/**
 * 获取视频信息
 */
async function getVideoInfo(filePath) {
    return new Promise((resolve, reject) => {
        fluent_ffmpeg_1.default.ffprobe(filePath, (err, metadata) => {
            if (err) {
                reject(err);
                return;
            }
            const videoStream = metadata.streams.find((s) => s.codec_type === 'video');
            const stats = fs_1.default.statSync(filePath);
            resolve({
                duration: Math.round(metadata.format.duration || 0),
                width: videoStream?.width || 0,
                height: videoStream?.height || 0,
                size: stats.size,
            });
        });
    });
}
