From c4561ab56735ff0fac6b436534eaa022278a9662 Mon Sep 17 00:00:00 2001 From: XiaoMo Date: Wed, 7 Jan 2026 19:14:56 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E9=85=8D=E7=BD=AE):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E5=92=8C=E7=BC=93=E5=AD=98=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改端口配置以支持环境变量覆盖 简化缓存目录配置,默认使用项目内的.cache目录 ``` ```msg perf(缩略图): 优化视频缩略图生成流程 使用ffmpeg生成PNG帧后再转换为WEBP格式 添加错误处理和日志输出 清理临时文件 更新缩略图元数据格式 --- index.js | 19 ++++++++++++++----- new.js | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/index.js b/index.js index 2f935dd..066beb1 100644 --- a/index.js +++ b/index.js @@ -18,7 +18,7 @@ const EventEmitter = require('events'); // Configuration const PORT = process.env.PORT || 9520; const API_BASE = 'http://183.6.121.121:9558/api'; -const CACHE_DIR = process.env.CACHE_DIR ? path.resolve(process.env.CACHE_DIR) : path.join(__dirname, '.cache'); +const CACHE_DIR = path.join(__dirname, '.cache'); // Ensure cache directory exists if (!fs.existsSync(CACHE_DIR)) { @@ -177,12 +177,21 @@ async function generateThumbAndCache(reply, apiData, contentPath) { if (contentType.includes('video/')) { console.log('Generating video thumb:', srcPath); - const thumbTemp = path.join(dir, base.replace('.data', `.thumb.webp.tmp`)); + const thumbFrameTemp = path.join(dir, base.replace('.data', `.thumb.frame.png.tmp`)); const { spawn } = require('child_process'); - const args = ['-ss', '1', '-i', srcPath, '-frames:v', '1', '-vf', `scale=${width}:-2`, '-y', thumbTemp]; - await new Promise((resolve, reject) => { const p = spawn('ffmpeg', args, { stdio: 'ignore' }); p.on('error', reject); p.on('close', c => c === 0 ? resolve() : reject(new Error(`ffmpeg exit ${c}`))); }); + const args = ['-hide_banner', '-loglevel', 'error', '-nostdin', '-ss', '1', '-i', srcPath, '-frames:v', '1', '-vf', `scale=${width}:-2`, '-f', 'image2', '-vcodec', 'png', '-y', thumbFrameTemp]; + await new Promise((resolve, reject) => { + let err = ''; + const p = spawn('ffmpeg', args, { stdio: ['ignore', 'ignore', 'pipe'] }); + if (p.stderr) p.stderr.on('data', d => { err += d.toString(); }); + p.on('error', reject); + p.on('close', c => c === 0 ? resolve() : reject(new Error(`ffmpeg exit ${c}${err ? ': ' + err.trim() : ''}`))); + }); + const thumbTemp = path.join(dir, base.replace('.data', `.thumb.tmp`)); + await sharp(thumbFrameTemp).webp({ quality: 80 }).toFile(thumbTemp); try { fs.renameSync(thumbTemp, thumbFinal); } catch (e) { if (fs.existsSync(thumbFinal)) { try { fs.unlinkSync(thumbFinal); } catch (_) { } fs.renameSync(thumbTemp, thumbFinal); } else { throw e; } } - await fs.promises.writeFile(metaThumbPath, JSON.stringify({ api: apiData, headers: apiData.data.headers || {}, srcSize: stat.size, inputFormat: 'video' })); + await fs.promises.writeFile(metaThumbPath, JSON.stringify({ api: apiData, headers: apiData.data.headers || {}, srcSize: stat.size, inputFormat: 'webp' })); + try { if (fs.existsSync(thumbFrameTemp)) fs.unlinkSync(thumbFrameTemp); } catch (_) { } const tstat = fs.statSync(thumbFinal); reply.headers({ 'Content-Type': 'image/webp', 'Content-Length': tstat.size, 'Accept-Ranges': 'bytes', 'Access-Control-Allow-Origin': '*' }); return fs.createReadStream(thumbFinal); diff --git a/new.js b/new.js index e414ea7..2ba9cef 100644 --- a/new.js +++ b/new.js @@ -7,7 +7,7 @@ const crypto = require('crypto'); const EventEmitter = require('events'); // Configuration -const PORT = 9520; +const PORT = process.env.PORT || 9520; const API_BASE = 'http://127.0.0.1:9558/api'; const CACHE_DIR = process.env.CACHE_DIR ? path.resolve(process.env.CACHE_DIR) : path.join(__dirname, '.cache');