@shun-js/remotion-server 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shun-js/remotion-server",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "remotion.cool server",
5
5
  "license": "MIT",
6
6
  "author": "uikoo9 <uikoo9@qq.com>",
@@ -23,7 +23,6 @@
23
23
  "@shun-js/shun-config": "^0.3.1",
24
24
  "@shun-js/shun-service": "^0.3.1",
25
25
  "@supabase/supabase-js": "^2.93.3",
26
- "qiao-file": "^5.0.6",
27
26
  "qiao-log": "^5.1.9",
28
27
  "qiao-timer": "^5.8.4",
29
28
  "qiao-z": "^5.8.9"
@@ -32,5 +31,5 @@
32
31
  "access": "public",
33
32
  "registry": "https://registry.npmjs.org/"
34
33
  },
35
- "gitHead": "260326ec9d0cb743db2fb84bb240828ee4caab84"
34
+ "gitHead": "326e09eaf91c306ba1fc1cc128659fdfeeb14d0d"
36
35
  }
@@ -20,7 +20,6 @@ exports.genVideo = async () => {
20
20
 
21
21
  // rows
22
22
  const rows = await fetchPendingWorks();
23
- logger.info(methodName, 'rows', rows);
24
23
  if (!rows || !rows.length) return;
25
24
 
26
25
  // work
@@ -11,67 +11,15 @@ exports.feishuMsg = (msg) => {
11
11
  feishuBot({
12
12
  url: global.QZ_CONFIG.feishu.url,
13
13
  feishuUrl: global.QZ_CONFIG.feishu.feishuUrl,
14
- feishuMsg: msg,
14
+ feishuMsg: `【通知】${msg}`,
15
15
  });
16
16
  };
17
17
 
18
- // is bot
19
- function isBot(req) {
20
- const ua = req.useragent;
21
- const hasOSName = ua && ua.os && ua.os.name;
22
- const isGoogleBot = ua && ua.browser && ua.browser.name === 'Googlebot';
23
- const isMetaBot = ua && ua.browser && ua.browser.name === 'meta-externalagent';
24
- const isSafariBot = ua && ua.engine && ua.engine.name === 'WebKit' && ua.engine.version === '605.1.15';
25
- return !hasOSName || isGoogleBot || isMetaBot || isSafariBot;
26
- }
27
-
28
18
  /**
29
19
  * errorFeishuMsg
30
- * @param {*} req
31
20
  * @param {*} msg
32
21
  * @returns
33
22
  */
34
- exports.errorFeishuMsg = (req, msg) => {
35
- // check
36
- if (isBot(req)) return;
37
-
38
- // msg
39
- const uaJson = JSON.stringify(req.useragent || {});
40
- exports.feishuMsg(`【通知】服务异常,${msg},请查看日志,ua:${uaJson}`);
41
- };
42
-
43
- /**
44
- * chatFeishuMsg
45
- * @param {*} req
46
- * @returns
47
- */
48
- exports.chatFeishuMsg = (req) => {
49
- // check
50
- if (isBot(req)) return;
51
-
52
- // msg
53
- const uaJson = JSON.stringify(req.useragent || {});
54
- const userid = req.headers.userid;
55
- const prompt = decodeURIComponent(req.body.userPrompt);
56
-
57
- const msg = `【通知】/chat被访问\nuserid:${userid}\nua:\n${uaJson}\nprompt:\n${prompt}`;
58
- exports.feishuMsg(msg);
59
- };
60
-
61
- /**
62
- * chatResFeishuMsg
63
- * @param {*} req
64
- * @returns
65
- */
66
- exports.chatResFeishuMsg = (req, chatRes) => {
67
- // check
68
- if (isBot(req)) return;
69
-
70
- // msg
71
- const uaJson = JSON.stringify(req.useragent || {});
72
- const userid = req.headers.userid;
73
- const prompt = decodeURIComponent(req.body.userPrompt);
74
-
75
- const msg = `【通知】/chat生成成功\nuserid:${userid}\nua:\n${uaJson}\nprompt:\n${prompt}\nres:${chatRes}`;
76
- exports.feishuMsg(msg);
23
+ exports.errorFeishuMsg = (msg) => {
24
+ exports.feishuMsg(`【通知】服务异常,${msg},请查看日志。`);
77
25
  };
@@ -11,6 +11,51 @@ const Logger = require('qiao-log');
11
11
  const logOptions = require('../log-options.js')();
12
12
  const logger = Logger(logOptions);
13
13
 
14
+ // 浏览器配置 - 使用固定的全局路径,避免每次重新下载
15
+ const BROWSER_EXECUTABLE_PATH = path.join(
16
+ '/home/ubuntu/remotions/browser',
17
+ 'chrome-headless-shell',
18
+ 'linux-arm64',
19
+ 'chrome-headless-shell-linux-arm64',
20
+ 'headless_shell',
21
+ );
22
+
23
+ /**
24
+ * 初始化浏览器 - 如果全局浏览器不存在,从 node_modules 复制
25
+ */
26
+ function ensureBrowserExists() {
27
+ const methodName = 'ensureBrowserExists';
28
+
29
+ // 如果全局浏览器已存在,直接返回
30
+ if (fs.existsSync(BROWSER_EXECUTABLE_PATH)) {
31
+ logger.info(methodName, 'Browser already exists at:', BROWSER_EXECUTABLE_PATH);
32
+ return;
33
+ }
34
+
35
+ logger.info(methodName, 'Global browser not found, attempting to copy from node_modules...');
36
+
37
+ // 查找 node_modules 中的浏览器
38
+ const nodeModulesBrowserPath = path.join(__dirname, '..', '..', 'node_modules', '.remotion', 'chrome-headless-shell');
39
+
40
+ if (fs.existsSync(nodeModulesBrowserPath)) {
41
+ // 创建目标目录
42
+ const globalBrowserDir = path.join('/home/ubuntu/remotions/browser');
43
+ fs.mkdirSync(globalBrowserDir, { recursive: true });
44
+
45
+ // 复制浏览器文件
46
+ logger.info(methodName, 'Copying browser from:', nodeModulesBrowserPath);
47
+ logger.info(methodName, 'Copying browser to:', globalBrowserDir);
48
+
49
+ fs.cpSync(nodeModulesBrowserPath, path.join(globalBrowserDir, 'chrome-headless-shell'), {
50
+ recursive: true,
51
+ });
52
+
53
+ logger.info(methodName, 'Browser copied successfully to:', BROWSER_EXECUTABLE_PATH);
54
+ } else {
55
+ logger.warn(methodName, 'Browser not found in node_modules, will download on first use');
56
+ }
57
+ }
58
+
14
59
  /**
15
60
  * 渲染 Remotion 视频
16
61
  * @param {Object} options
@@ -24,10 +69,13 @@ exports.renderVideo = async ({ sourceCode, outputPath, width, height, fps }) =>
24
69
  const methodName = 'renderVideo';
25
70
 
26
71
  // const
27
- const tempDir = path.join(__dirname, 'temp', Date.now().toString());
72
+ const tempDir = path.join('/tmp', 'remotion-render', Date.now().toString());
28
73
  const entryPoint = path.join(tempDir, 'src', 'Root.jsx');
29
74
 
30
75
  try {
76
+ // 0. 确保浏览器存在(首次运行时自动复制到全局目录)
77
+ ensureBrowserExists();
78
+
31
79
  // 1. 创建临时项目结构
32
80
  fs.mkdirSync(path.join(tempDir, 'src'), { recursive: true });
33
81
 
@@ -41,27 +89,37 @@ exports.renderVideo = async ({ sourceCode, outputPath, width, height, fps }) =>
41
89
  version: '1.0.0',
42
90
  dependencies: {
43
91
  react: '^18.2.0',
92
+ 'react-dom': '^18.2.0',
44
93
  remotion: '^4.0.0',
45
94
  },
46
95
  };
47
96
  fs.writeFileSync(path.join(tempDir, 'package.json'), JSON.stringify(packageJson, null, 2));
48
97
 
49
- // 4. Bundle 代码
98
+ // 4. 创建 remotion.config.js (可选但推荐)
99
+ const remotionConfig = `
100
+ module.exports = {
101
+ // Remotion 配置
102
+ };
103
+ `;
104
+ fs.writeFileSync(path.join(tempDir, 'remotion.config.js'), remotionConfig);
105
+
106
+ // 5. Bundle 代码
50
107
  logger.info(methodName, 'Bundling code...');
51
108
  const bundleLocation = await bundle({
52
109
  entryPoint,
53
110
  webpackOverride: (config) => config,
54
111
  });
55
112
 
56
- // 5. 获取 composition
113
+ // 6. 获取 composition
57
114
  logger.info(methodName, 'Getting composition...');
58
115
  const composition = await selectComposition({
59
116
  serveUrl: bundleLocation,
60
117
  id: 'MyComposition', // 默认 composition ID
61
118
  inputProps: {},
119
+ browserExecutable: fs.existsSync(BROWSER_EXECUTABLE_PATH) ? BROWSER_EXECUTABLE_PATH : undefined,
62
120
  });
63
121
 
64
- // 6. 渲染视频
122
+ // 7. 渲染视频
65
123
  logger.info(methodName, 'Rendering frames...');
66
124
  await renderMedia({
67
125
  composition: {
@@ -75,6 +133,7 @@ exports.renderVideo = async ({ sourceCode, outputPath, width, height, fps }) =>
75
133
  codec: 'h264',
76
134
  outputLocation: outputPath,
77
135
  inputProps: {},
136
+ browserExecutable: fs.existsSync(BROWSER_EXECUTABLE_PATH) ? BROWSER_EXECUTABLE_PATH : undefined,
78
137
  onProgress: ({ progress }) => {
79
138
  if (progress % 0.1 < 0.01) {
80
139
  // 每10%打印一次
@@ -98,13 +157,13 @@ exports.renderVideo = async ({ sourceCode, outputPath, width, height, fps }) =>
98
157
  function wrapUserCode(sourceCode) {
99
158
  return `
100
159
  import React from 'react';
101
- import { Composition, AbsoluteFill, useCurrentFrame, interpolate, useVideoConfig } from 'remotion';
160
+ import { Composition, registerRoot, AbsoluteFill, useCurrentFrame, interpolate, useVideoConfig } from 'remotion';
102
161
 
103
162
  // 用户代码
104
163
  ${sourceCode}
105
164
 
106
165
  // Root 组件 - Remotion 入口
107
- export const RemotionRoot = () => {
166
+ const RemotionRoot = () => {
108
167
  return (
109
168
  <>
110
169
  <Composition
@@ -118,5 +177,8 @@ export const RemotionRoot = () => {
118
177
  </>
119
178
  );
120
179
  };
180
+
181
+ // 注册 Root 组件(Remotion 4.x 要求)
182
+ registerRoot(RemotionRoot);
121
183
  `;
122
184
  }
@@ -11,6 +11,9 @@ const { uploadToR2 } = require('./uploader.js');
11
11
  // model
12
12
  const { updateRenderStatus } = require('../model/RemotionModel.js');
13
13
 
14
+ // feishu
15
+ const { feishuMsg, errorFeishuMsg } = require('../util/feishu.js');
16
+
14
17
  // logger
15
18
  const Logger = require('qiao-log');
16
19
  const logOptions = require('../log-options.js')();
@@ -26,6 +29,7 @@ exports.processWork = async (work) => {
26
29
  // const
27
30
  const workId = work.id;
28
31
  const outputPath = path.join(global.QZ_CONFIG.OUTPUT_DIR, `${workId}.mp4`);
32
+ feishuMsg(`${workId} start`);
29
33
  logger.info(methodName, `\n[${new Date().toISOString()}] Processing work: ${workId}`);
30
34
  logger.info(methodName, `Title: ${work.title}`);
31
35
  logger.info(methodName, `Status: ${work.status}, Render Status: ${work.render_status}`);
@@ -40,7 +44,7 @@ exports.processWork = async (work) => {
40
44
  height: work.height || 1080,
41
45
  fps: work.fps || 30,
42
46
  });
43
-
47
+ feishuMsg(`${workId} render ok`);
44
48
  logger.info(methodName, 'Video rendered successfully');
45
49
 
46
50
  // 2. 上传到 R2
@@ -49,7 +53,7 @@ exports.processWork = async (work) => {
49
53
  filePath: outputPath,
50
54
  workId: workId,
51
55
  });
52
-
56
+ feishuMsg(`${workId} upload ok, ${videoUrl}`);
53
57
  logger.info(methodName, 'Video uploaded:', videoUrl);
54
58
 
55
59
  // 3. 获取视频信息
@@ -63,13 +67,15 @@ exports.processWork = async (work) => {
63
67
  duration_in_frames: durationInFrames,
64
68
  duration_seconds: durationInFrames / (work.fps || 30),
65
69
  });
66
-
70
+ feishuMsg(`${workId} db ok`);
67
71
  logger.info(methodName, `✅ Work ${workId} completed successfully`);
68
72
  logger.info(methodName, ` Status: ${work.status}, Render Status: completed`);
69
73
 
70
74
  // 5. 清理本地文件
71
75
  fs.unlinkSync(outputPath);
76
+ feishuMsg(`${workId} clear ok`);
72
77
  } catch (error) {
78
+ errorFeishuMsg(`${workId} \n ${error}`);
73
79
  logger.error(methodName, `❌ Error processing work ${workId}:`, error);
74
80
 
75
81
  // 更新为渲染失败状态