{“content”:”---\nname: remotion-pc-remote-workflow\ndescription: Remotion video rendering on remote Windows PC via SSH — build, compositions, render\ncategory: media\n---\n\n# Remotion PC 远程渲染工作流\n\n## 关键 API(Remotion 4.x)\n\ntypescript\n// Root.tsx\nimport { registerRoot } from \"remotion\";\n// registerRoot(Component) — 调用一次\n\n// remotion.config.ts(4.x 保持最小化或删除)\n// Config.setPublicFolder() 在 4.x 中不存在,移除\n\n\n## 命令\n\nbash\n# 构建 bundle(--outDir 不是 --out-dir)\nnpx remotion bundle src/Root.tsx --outDir out/bundle\n\n# 查看 compositions — 必须用 src/Root.tsx,不能用 bundle 输出的 index.html\nnpx remotion compositions src/Root.tsx\n\n# 渲染视频 — 用 src/Root.tsx 作为 entry point\nnpx remotion render src/Root.tsx <composition-name> --output out/<name>.mp4\n\n# package.json 脚本示例\n\"scripts\": {\n \"bundle\": \"remotion bundle src/Root.tsx --outDir out/bundle\",\n \"compositions\": \"remotion compositions src/Root.tsx\",\n \"render-xh\": \"remotion render src/Root.tsx Xiaohongshu --output out/xh.mp4\",\n \"render-news\": \"remotion render src/Root.tsx NewsDaily --output out/news-daily.mp4\"\n}\n\n\n## 常见问题\n\n### registerRoot not found\n- 4.x 中 registerRoot 来自 remotion 包,不是 @remotion/cli/config\n- 确保:import { registerRoot } from \"remotion\"\n\n### UTF-8 中文乱码(SCP 上传)\n- PowerShell Set-Content -Encoding UTF8 会加 BOM,破坏 JSON/TS 文件\n- 解决:上传 base64 → PC 用 Python 解码写入\nbash\n# 本地(Termux)\nbase64 -w0 file.tsx | ssh sozo@host \"python -c \\\"import sys; open('file.tsx','wb').write(sys.stdin.buffer.read()); print('done')\\\"\"\n\n- 或 PowerShell 用 [System.IO.File]::WriteAllText()(无 BOM)\n\n### npm run build 报错 “Missing script”\n- 项目装了 @remotion/cli 但 package.json 的 scripts.build 没生效\n- 直接用 node_modules 里的 CLI:\npowershell\n.\\node_modules\\.bin\\remotion bundle src/Root.tsx --outDir out/bundle\n\n\n### esbuild “Expected ”}” but found ”…” 错误(中文引号陷阱)\n- esbuild 报错信息:Expected \"}\" but found \"隐形健康\" — 这个错误信息有误导性\n- 真正原因:TypeScript 字符串里包含 ASCII \" (U+0022),把字符串提前关闭了\n- 检查方法:找含 text: \" 且引号数 > 2 的行\n- 根因:中文引号 «» (U+00AB/BB) 或 \"...\" 在写入文件后变成了 ASCII \"\n- 修复:Python 脚本扫描所有 text: \"...\" 字段,把内部的 \" 替换为 '\npython\n# fix_quotes.py — 批量修复 TSX 文件中的中文引号\nimport re\nwith open(\"NewsDaily.tsx\", \"r\", encoding=\"utf-8\") as f:\n content = f.read()\nlines = content.split('\\n')\nfor i, line in enumerate(lines):\n m = re.match(r'^(\\s*text:\\s*\")(.+)(\".*)$', line)\n if m:\n prefix, inner, suffix = m.group(1), m.group(2), m.group(3)\n if '\"' in inner:\n lines[i] = prefix + inner.replace('\"', \"'\") + suffix\nwith open(\"NewsDaily.tsx\", \"w\", encoding=\"utf-8\") as f:\n f.write('\\n'.join(lines))\n\n\n### npx remotion compositions 报错 “does not contain registerRoot”\n- 原因:bundle 输出的 out/bundle/index.html 不暴露 registerRoot 字符串\n- 解决:compositions 和 render 都用 src/Root.tsx 作为 entry point\nbash\n# 正确\nnpx remotion compositions src/Root.tsx\nnpx remotion render src/Root.tsx NewsDaily --output out/news.mp4\n\n# 错误(bundle 输出的 index.html 没有 registerRoot)\nnpx remotion compositions out/bundle/index.html\n\n\n### “Found registerRoot” 错误\n- bundler 通过字符串匹配检测 registerRoot——即使没调用也会报错\n- 确认 Root.tsx 正确调用了 registerRoot(Component)\n\n### 首次渲染下载 Chrome\n- remotion render 会自动下载 Chrome Headless Shell (~107MB)\n- 下载完成后重复运行渲染命令即可\n\n### 图片加载 404 问题\n- Remotion 渲染时通过内部服务器提供 public/ 目录,但路径必须匹配\n- 不要用: <Img src=\"/xh/foo.jpeg\" /> — 渲染时 404\n- 正确做法: 图片放进 src/ 子目录,用 require() 导入\ntsx\n// 例:src/xh/img1.jpeg\nconst PROJECT_IMG = [\n require(\"./img1.jpeg\"),\n require(\"./img2.jpeg\"),\n];\n// JSX: <img src={PROJECT_IMG[index]} style={{width:\"100%\",height:\"100%\",objectFit:\"cover\"}} />\n\n- 理由:Webpack 打包时处理 require(),图片数据内联进 bundle,渲染时无需访问外部文件\n\n## 通过 SSH 启动 Windows 渲染(Task Scheduler 方法)\n\n问题: Start-Process -FilePath \"npx\" 在 SSH 会话中会失败——npx 是 CMD 脚本而非 Win32 可执行文件,导致 %1 is not a valid Win32 application 错误。\n\n解决方案: 用 Windows Task Scheduler 创建并立即运行任务,完全脱离 SSH 会话。\n\npython\n# start_render_pc.py — 在 PC 上运行此脚本启动渲染\nimport subprocess, time\n\ntask_name = \"HermesNewsRender\"\n\n# 删除旧任务(如有)\nsubprocess.run([\"schtasks\", \"/delete\", \"/tn\", task_name, \"/f\"], capture_output=True)\n\n# 创建一次性任务\nresult = subprocess.run([\n \"schtasks\", \"/create\",\n \"/tn\", task_name,\n \"/tr\", r'\"D:\\remotion-hsd\\render_news.bat\"',\n \"/sc\", \"once\",\n \"/st\", time.strftime(\"%H:%M\"),\n \"/f\"\n], capture_output=True, text=True)\nprint(\"Create:\", result.returncode, result.stdout)\n\n# 立即运行\nresult2 = subprocess.run([\n \"schtasks\", \"/run\", \"/tn\", task_name\n], capture_output=True, text=True)\nprint(\"Run:\", result2.returncode, result2.stdout, result2.stderr)\n\n\n对应的 render_news.bat:\nbat\n@echo off\ncd /d D:\\remotion-hsd\n\"C:\\Program Files\\nodejs\\npx.cmd\" remotion render NewsDaily out/bundle/index.html --browser-executable-path \"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe\"\necho RENDER_DONE: %ERRORLEVEL%\n\n\n上传脚本后通过 SSH 运行:\nbash\nscp start_render_pc.py sozo@host:\"C:/Users/Sozo/AppData/Local/Temp/\"\nssh sozo@host 'python C:\\Users\\Sozo\\AppData\\Local\\Temp\\start_render_pc.py'\n\n\n验证渲染是否进行:\nbash\n# 方法1:检查Chrome进程数\nssh sozo@host 'python -c \"import os; count=len([p for p in os.popen(\\\"tasklist /FI \\\\\\\"IMAGENAME eq chrome.exe\\\\\\\"\\\").readlines() if \\\"chrome\\\" in p.lower()]); print(count)\"'\n\n# 方法2:检查输出文件大小\nssh sozo@host 'python -c \"import os; p=r\\\"D:\\\\remotion-hsd\\\\out\\\\news-daily.mp4\\\"; print(os.path.exists(p), os.path.getsize(p) if os.path.exists(p) else 0)\"'\n\n\n## PC 路径\n- SSH:sozo@100.83.112.84\n- SCP 目标:C:\\Users\\Sozo\\AppData\\Local\\Temp\\\n- 新闻音频:D:\\remotion-hsd\\src\\news\\news_audio.mp3\n\n## 当前项目 compositions\n- NewsDaily — 1920×1080,284秒,8520帧,16:9 新闻播报\n- Xiaohongshu — 1080×1920,21秒,630帧,9:16 小红书竖屏\n- src/planning/ 和 src/landing/ — 空目录,待创建 composition\n”}