| 363 | return { |
| 364 | ok: false, |
| 365 | message: "缺少 ffmpeg,请安装: apt-get install -y ffmpeg", |
| 366 | }; |
| 367 | } |
| 368 | return { ok: true, message: "" }; |
| 369 | } |
| 370 | |
| 371 | // TGS 转 WebM (使用 rlottie-python + ffmpeg) |
| 372 | async function convertTgsToWebm(tgsBuffer: Buffer): Promise<Buffer> { |
| 373 | const os = await import("os"); |
| 374 | const tmpDir = os.tmpdir(); |
| 375 | const uniqueId = |
| 376 | Date.now().toString() + "_" + Math.random().toString(36).slice(2); |
| 377 | const tgsPath = path.join(tmpDir, `sticker_${uniqueId}.tgs`); |
| 378 | const gifPath = path.join(tmpDir, `sticker_${uniqueId}.gif`); |
| 379 | const webmPath = path.join(tmpDir, `sticker_${uniqueId}.webm`); |
| 380 | |
| 381 | try { |
| 382 | fs.writeFileSync(tgsPath, tgsBuffer); |
| 383 | |
| 384 | const pythonScript = ` |
| 385 | import sys |
| 386 | |
| 387 | from rlottie_python import LottieAnimation |
| 388 | anim = LottieAnimation.from_tgs(sys.argv[1]) |
| 389 | anim.save_animation(sys.argv[2]) |
| 390 | `; |
| 391 | |
| 392 | await execFileAsync(PYTHON_PATH, ["-c", pythonScript, tgsPath, gifPath]); |
| 393 | |
| 394 | await execFileAsync("ffmpeg", [ |
| 395 | "-i", |
| 396 | gifPath, |
| 397 | "-c:v", |
| 398 | "libvpx-vp9", |
| 399 | "-pix_fmt", |
| 400 | "yuva420p", |
| 401 | "-b:v", |
| 402 | "400k", |
| 403 | "-auto-alt-ref", |
| 404 | "0", |
| 405 | "-an", |
| 406 | "-y", |
| 407 | webmPath, |
| 408 | ]); |
| 409 | |
| 410 | const webmBuffer = fs.readFileSync(webmPath); |
| 411 | return webmBuffer; |
| 412 | } finally { |
| 413 | try { |
| 414 | fs.unlinkSync(tgsPath); |
| 415 | } catch (e) {} |
| 416 | try { |
| 417 | fs.unlinkSync(gifPath); |
| 418 | } catch (e) {} |
| 419 | try { |
| 420 | fs.unlinkSync(webmPath); |
| 421 | } catch (e) {} |
| 422 | } |