img-scale.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. /**
  2. * fun 结点:img-scale — 图片等比缩放(宽高同乘同一比例)
  3. *
  4. * 入参(inVars 顺序,与 img-cropping 一致:先路径再参数):
  5. * - imagePath:源图(相对流程目录或绝对路径)
  6. * - savePath:输出路径
  7. * - scale:缩放系数。支持:
  8. * - 0.8 → 长宽各 ×0.8
  9. * - "80%" → 80%;整数 10~100 视为百分比,如 80 即 80%
  10. * - 1~10 之间的小数/整数为倍率,如 2 即 2 倍、1.5 即 1.5 倍(整数 10 为 10% 而非 10 倍)
  11. */
  12. const path = require('path')
  13. const fs = require('fs')
  14. const { spawnSync } = require('child_process')
  15. const { getPythonExeFromConfig } = require('../../../../python-exe-from-config.js')
  16. const tagName = 'img-scale'
  17. const configPath = process.env.STATIC_ROOT
  18. ? path.join(path.dirname(path.resolve(process.env.STATIC_ROOT)), 'config.js')
  19. : path.join(__dirname, '..', '..', '..', '..', '..', 'config.js')
  20. const config = fs.existsSync(configPath) ? require(configPath) : {}
  21. const projectRoot = (config.projectRoot && fs.existsSync(config.projectRoot))
  22. ? config.projectRoot
  23. : path.dirname(path.resolve(configPath))
  24. const scaleScriptPath = path.join(projectRoot, 'python', 'scripts', 'img-scale-proportional.py')
  25. function buildAbsolutePath (p, folderPath) {
  26. if (p == null || p === '') return null
  27. const s = typeof p === 'string' ? p.trim() : String(p)
  28. if (!s) return null
  29. if (path.isAbsolute(s) || /^[A-Za-z]:/.test(s)) return path.normalize(s)
  30. return folderPath ? path.join(folderPath, s) : path.resolve(projectRoot, s)
  31. }
  32. /**
  33. * @param {{ imagePath: string, savePath: string, scale: string|number, folderPath?: string }} input
  34. */
  35. async function executeImgScale ({ imagePath, savePath, scale, folderPath }) {
  36. if (imagePath == null || String(imagePath).trim() === '') {
  37. return { success: false, error: 'img-scale 缺少 imagePath(inVars[0])' }
  38. }
  39. if (savePath == null || String(savePath).trim() === '') {
  40. return { success: false, error: 'img-scale 缺少 savePath(inVars[1])' }
  41. }
  42. if (scale === undefined || scale === null || scale === '') {
  43. return { success: false, error: 'img-scale 缺少 scale(inVars[2],如 0.8 或 80%)' }
  44. }
  45. const scaleStr = typeof scale === 'number' && Number.isFinite(scale) ? String(scale) : String(scale).trim()
  46. if (!scaleStr) return { success: false, error: 'img-scale scale 无效' }
  47. const absIn = buildAbsolutePath(String(imagePath).trim(), folderPath)
  48. const absOut = buildAbsolutePath(String(savePath).trim(), folderPath)
  49. if (!absIn || !absOut) return { success: false, error: 'img-scale 路径无效' }
  50. if (!fs.existsSync(absIn)) return { success: false, error: `img-scale 源文件不存在: ${absIn}` }
  51. if (!fs.existsSync(scaleScriptPath)) {
  52. return { success: false, error: `脚本不存在: ${scaleScriptPath}` }
  53. }
  54. const pythonExe = getPythonExeFromConfig(config)
  55. const r = spawnSync(pythonExe, [scaleScriptPath, absIn, absOut, scaleStr], {
  56. encoding: 'utf-8',
  57. timeout: 120_000,
  58. cwd: projectRoot,
  59. })
  60. const combined = ((r.stdout || '') + '\n' + (r.stderr || '')).trim()
  61. if (r.status !== 0) {
  62. return { success: false, error: combined || 'img-scale 执行失败' }
  63. }
  64. let meta = {}
  65. const lines = (r.stdout || '').split(/\r?\n/).map((l) => l.trim()).filter(Boolean)
  66. const jsonLine = lines.filter((l) => l.startsWith('{')).pop()
  67. if (jsonLine) {
  68. try {
  69. meta = JSON.parse(jsonLine)
  70. } catch (_) {}
  71. }
  72. if (!meta.success) {
  73. return { success: false, error: meta.error || combined || 'img-scale 未返回成功' }
  74. }
  75. return {
  76. success: true,
  77. path: absOut,
  78. value: absOut,
  79. result: absOut,
  80. scaled: {
  81. scale: meta.scale,
  82. sourceWidth: meta.sourceWidth,
  83. sourceHeight: meta.sourceHeight,
  84. width: meta.width,
  85. height: meta.height,
  86. },
  87. }
  88. }
  89. module.exports = { tagName, executeImgScale }