07-quicklyVideo.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. /**
  2. * 云函数:quicklyVideo(一键成片代理)
  3. * 替代:
  4. * POST /api/quickly/create → action=create
  5. * POST /api/quickly/query → action=query
  6. *
  7. * 把 server.js 里的签名 + relay 逻辑搬到云函数,
  8. * 隐藏 APP_KEY / APP_SECRET,前端只需传 videoUrls。
  9. *
  10. * ⚠️ 不是参考别项目,是本项目 server.js 1820~1914 行的等价实现。
  11. */
  12. const crypto = require('crypto');
  13. const QUICKLY_APP_KEY = 'ZmNmOGRhNjYzZTAx';
  14. const QUICKLY_APP_SECRET = 'eaa12154c248cad9159a9d6ea8bedf46';
  15. const QUICKLY_ACCOUNT_ID = '12859_117409';
  16. const QUICKLY_CALLBACK_URL = 'https://server.fmode.cn/api/functions/cut/onemerge';
  17. const QUICKLY_RELAY_URL = 'https://server.fmode.cn/api/functions';
  18. // 上游筷子代理云函数 ID(fmode 平台已有)
  19. const UPSTREAM_FN_ID = 'sWvRr8RvPT';
  20. async function handler(request, response) {
  21. try {
  22. const action = pickParam(request, 'action');
  23. if (action === 'create') {
  24. const videoUrls = pickParam(request, 'videoUrls');
  25. const options = pickParam(request, 'options') || {};
  26. if (!Array.isArray(videoUrls) || videoUrls.length === 0) {
  27. return response.json({ code: 400, success: false, error: '缺少 videoUrls 参数' });
  28. }
  29. const timestamp = Date.now().toString();
  30. const sign = crypto.createHash('md5').update(timestamp + '#' + QUICKLY_APP_SECRET).digest('hex');
  31. const apiBody = {
  32. account_id: QUICKLY_ACCOUNT_ID,
  33. callback_url: QUICKLY_CALLBACK_URL,
  34. material_list: videoUrls.map(url => ({ type: 'video', value: url })),
  35. tags: options.tags || '视频,AI生成',
  36. proportion: options.proportion || '9:16',
  37. video_duration: options.videoDuration || { min: 10, max: 30 },
  38. pre_id: timestamp,
  39. compose_number: 1,
  40. ai_voice: options.aiVoice ?? 1,
  41. ai_bgm: options.aiBgm ?? 1,
  42. ai_flower: 1,
  43. ai_subtitle: options.aiSubtitle ?? 0,
  44. original_voice: 0
  45. };
  46. const relayBody = {
  47. action: 'relay',
  48. relayData: JSON.stringify({
  49. apiPath: '/v2/video/vlog/create',
  50. apiBody,
  51. appKey: QUICKLY_APP_KEY,
  52. timestamp,
  53. sign
  54. })
  55. };
  56. console.log(`📦 quicklyVideo.create timestamp=${timestamp} videos=${videoUrls.length}`);
  57. const r = await fetch(QUICKLY_RELAY_URL, {
  58. method: 'POST',
  59. headers: { 'Content-Type': 'application/json' },
  60. body: JSON.stringify(relayBody)
  61. });
  62. const result = await r.json();
  63. response.json({ code: 200, success: true, data: result });
  64. return;
  65. }
  66. if (action === 'query') {
  67. const taskId = pickParam(request, 'taskId');
  68. if (!taskId) return response.json({ code: 400, success: false, error: '缺少 taskId' });
  69. const r = await fetch(QUICKLY_RELAY_URL, {
  70. method: 'POST',
  71. headers: { 'Content-Type': 'application/json' },
  72. body: JSON.stringify({
  73. id: UPSTREAM_FN_ID,
  74. _ApplicationId: 'ncloudmaster',
  75. action: 'query',
  76. taskId
  77. })
  78. });
  79. const result = await r.json();
  80. response.json({ code: 200, success: true, data: result });
  81. return;
  82. }
  83. response.json({ code: 400, success: false, error: `未知 action: ${action}` });
  84. } catch (error) {
  85. console.error('❌ quicklyVideo 失败:', error.message);
  86. response.json({ code: 500, success: false, error: error.message });
  87. }
  88. }
  89. function pickParam(request, ...names) {
  90. const sources = [request.params, request.body, request];
  91. for (const src of sources) {
  92. if (!src || typeof src !== 'object') continue;
  93. for (const n of names) {
  94. const v = src[n];
  95. if (v !== undefined && v !== null && v !== '') return v;
  96. }
  97. }
  98. return null;
  99. }