compose.ts 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import type {
  2. VideoDocument,
  3. AssetManifest,
  4. RenderProps,
  5. RenderScene,
  6. PlatformPreset,
  7. TemplateType,
  8. } from "@pipeline/shared";
  9. import { PLATFORM_PRESETS } from "@pipeline/shared";
  10. import { basename } from "node:path";
  11. import { segmentImageRefs, resolveGithubImages } from "./github-images.js";
  12. /**
  13. * Project a document into the render-time `RenderProps` for one platform:
  14. * resolve asset filenames for staticFile() and carry each segment's AUDIO
  15. * duration. No frame/size computation here — the TEMPLATE computes the visual
  16. * timeline (it may extend beyond audio for silent padding, holds, etc.), and
  17. * the Remotion Composition owns fps/width/height/durationInFrames.
  18. */
  19. export function composeRenderProps(
  20. doc: VideoDocument,
  21. assets: AssetManifest,
  22. platform: PlatformPreset,
  23. template: TemplateType,
  24. channelName = "Pipeline"
  25. ): RenderProps {
  26. const scenes: RenderScene[] = doc.data.map((seg, i) => {
  27. const backgroundAsset = assets.backgrounds[i]
  28. ? { id: assets.backgrounds[i].id, filename: basename(assets.backgrounds[i].localPath) }
  29. : undefined;
  30. const images = segmentImageRefs(seg)
  31. .map((img) => {
  32. const resolved = assets.images.find(
  33. (a) =>
  34. (img.path && a.path === img.path) ||
  35. (img.url && a.url === img.url) ||
  36. (img.query && a.query === img.query) ||
  37. (img.repoSocialPreview && a.repoSocialPreview === img.repoSocialPreview)
  38. );
  39. return resolved
  40. ? { filename: basename(resolved.localPath), url: img.url, query: img.query }
  41. : undefined;
  42. })
  43. .filter((x): x is NonNullable<typeof x> => !!x);
  44. // Resolve the per-template extension (github board images → filenames;
  45. // github-trending and github-weekly share the shape).
  46. const extension = seg.extension?.type === "github-trending" || seg.extension?.type === "github-weekly"
  47. ? {
  48. type: seg.extension.type,
  49. repo: seg.extension.repo,
  50. highlights: seg.extension.highlights,
  51. intro: seg.extension.intro,
  52. review: seg.extension.review,
  53. images: resolveGithubImages(seg.extension, assets),
  54. }
  55. : undefined;
  56. return {
  57. id: seg.id,
  58. kind: seg.kind,
  59. index: seg.index,
  60. title: seg.title,
  61. desc: seg.desc,
  62. captionOrigin: seg.captionOrigin,
  63. caption: seg.caption,
  64. cardList: seg.cardList,
  65. menu: seg.menu,
  66. images,
  67. layoutHint: seg.layoutHint,
  68. extension,
  69. duration: seg.duration, // audio seconds (template computes visual frames)
  70. audioFilename: seg.captionAudioFileUrl ? basename(seg.captionAudioFileUrl) : "",
  71. backgroundAsset,
  72. };
  73. });
  74. return {
  75. scenes,
  76. template,
  77. platform,
  78. title: doc.meta?.title || "",
  79. subtitle: doc.meta?.subtitle || "",
  80. channelName,
  81. trendSummary: doc.meta?.trendSummary,
  82. globalStyle: { tone: "formal", pace: "normal" },
  83. };
  84. }
  85. /** Composition id the Remotion root registers (Root.tsx uses `${template}-${landscape|portrait}`). */
  86. export function compositionIdFor(template: TemplateType, platform: string): string {
  87. const preset = PLATFORM_PRESETS[platform as PlatformPreset];
  88. const aspectKey = preset.aspect === "16:9" ? "landscape" : "portrait";
  89. return `${template}-${aspectKey}`;
  90. }