download-img.js 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. /**
  2. * fun 结点:根据描述词(prompt)用 python/imagedl 搜索并下载一张图片到 savePath
  3. * 入参:prompt(描述词), savePath(相对路径时基于 folderPath)
  4. */
  5. const path = require('path')
  6. const fs = require('fs')
  7. const { spawnSync } = require('child_process')
  8. const configPath = process.env.STATIC_ROOT
  9. ? path.join(path.dirname(process.env.STATIC_ROOT), 'configs', 'config.js')
  10. : path.join(__dirname, '..', '..', '..', '..', 'configs', 'config.js')
  11. const projectRoot = path.dirname(path.dirname(path.resolve(configPath)))
  12. const config = fs.existsSync(configPath) ? require(configPath) : {}
  13. const scriptPath = path.join(projectRoot, 'python', 'scripts', 'download-img-by-prompt.py')
  14. const imagedlParent = path.join(projectRoot, 'python')
  15. const imagedlRequirements = path.join(projectRoot, 'python', 'imagedl', 'requirements.txt')
  16. function getPythonPath() {
  17. const base = config.pythonPath?.path || config.pythonVenvPath || path.join(projectRoot, 'python', process.arch === 'arm64' ? 'arm64' : 'x64')
  18. const envPy = path.join(base, 'env', 'Scripts', 'python.exe')
  19. const scriptsPy = path.join(base, 'Scripts', 'python.exe')
  20. const pyEmbedded = path.join(base, 'py', 'python.exe')
  21. if (fs.existsSync(envPy)) return envPy
  22. if (fs.existsSync(scriptsPy)) return scriptsPy
  23. if (fs.existsSync(pyEmbedded)) return pyEmbedded
  24. return 'python'
  25. }
  26. function buildSavePath(savePath, folderPath) {
  27. if (!savePath || typeof savePath !== 'string') return null
  28. const trimmed = savePath.trim()
  29. if (path.isAbsolute(trimmed) || trimmed.match(/^[A-Za-z]:/)) return trimmed
  30. return folderPath ? path.join(folderPath, trimmed) : path.resolve(projectRoot, trimmed)
  31. }
  32. async function executeDownloadImg({ prompt, savePath, folderPath }) {
  33. if (!prompt || typeof prompt !== 'string' || !prompt.trim()) return { success: false, error: 'download-img 缺少 prompt 参数' }
  34. if (savePath == null) return { success: false, error: 'download-img 缺少 savePath 参数' }
  35. const absolutePath = buildSavePath(String(savePath).trim(), folderPath)
  36. if (!absolutePath) return { success: false, error: 'download-img savePath 无效' }
  37. if (!fs.existsSync(scriptPath)) return { success: false, error: `脚本不存在: ${scriptPath}` }
  38. const pythonPath = getPythonPath()
  39. const args = [scriptPath, '--prompt', prompt.trim(), '--save-path', absolutePath.replace(/\\/g, '/')]
  40. const runScript = () => spawnSync(pythonPath, args, {
  41. encoding: 'utf-8',
  42. timeout: 120000,
  43. env: { ...process.env, PYTHONIOENCODING: 'utf-8', IMAGEDL_PARENT: imagedlParent },
  44. cwd: projectRoot
  45. })
  46. let r = runScript()
  47. let out = (r.stdout || '').trim()
  48. const err = (r.stderr || '').trim()
  49. const fullOut = out + (err ? '\n' + err : '')
  50. if (r.status !== 0 && fs.existsSync(imagedlRequirements) && (fullOut.includes('No module named') || fullOut.includes('ModuleNotFoundError'))) {
  51. spawnSync(pythonPath, ['-m', 'pip', 'install', '-r', imagedlRequirements, '-q'], {
  52. encoding: 'utf-8',
  53. timeout: 180000,
  54. cwd: projectRoot
  55. })
  56. r = runScript()
  57. out = (r.stdout || '').trim()
  58. }
  59. if (r.status !== 0) {
  60. return { success: false, error: (r.stderr || r.stdout || '').trim() || 'download-img 执行失败' }
  61. }
  62. // stdout 可能包含进度条等,取最后一行以 { 开头的行作为 JSON
  63. const lines = out.split(/\r?\n/).map(s => s.trim()).filter(Boolean)
  64. let jsonStr = lines[lines.length - 1]
  65. if (!jsonStr || !jsonStr.startsWith('{')) {
  66. const lastJson = lines.filter(l => l.startsWith('{')).pop()
  67. jsonStr = lastJson || out
  68. }
  69. let result
  70. try {
  71. result = JSON.parse(jsonStr)
  72. } catch (_) {
  73. return { success: false, error: out.slice(-300) || '无法解析输出' }
  74. }
  75. if (!result.success) return { success: false, error: result.error || '未下载到图片' }
  76. return { success: true, path: result.path }
  77. }
  78. module.exports = { executeDownloadImg }