compose.ts 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  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-trending images → filenames).
  45. const extension = seg.extension?.type === "github-trending"
  46. ? {
  47. type: "github-trending" as const,
  48. repo: seg.extension.repo,
  49. highlights: seg.extension.highlights,
  50. intro: seg.extension.intro,
  51. review: seg.extension.review,
  52. images: resolveGithubImages(seg.extension, assets),
  53. }
  54. : undefined;
  55. return {
  56. id: seg.id,
  57. kind: seg.kind,
  58. index: seg.index,
  59. title: seg.title,
  60. desc: seg.desc,
  61. captionOrigin: seg.captionOrigin,
  62. caption: seg.caption,
  63. cardList: seg.cardList,
  64. menu: seg.menu,
  65. images,
  66. layoutHint: seg.layoutHint,
  67. extension,
  68. duration: seg.duration, // audio seconds (template computes visual frames)
  69. audioFilename: seg.captionAudioFileUrl ? basename(seg.captionAudioFileUrl) : "",
  70. backgroundAsset,
  71. };
  72. });
  73. return {
  74. scenes,
  75. template,
  76. platform,
  77. title: doc.meta?.title || "",
  78. subtitle: doc.meta?.subtitle || "",
  79. channelName,
  80. trendSummary: doc.meta?.trendSummary,
  81. globalStyle: { tone: "formal", pace: "normal" },
  82. };
  83. }
  84. /** Composition id the Remotion root registers (Root.tsx uses `${template}-${landscape|portrait}`). */
  85. export function compositionIdFor(template: TemplateType, platform: string): string {
  86. const preset = PLATFORM_PRESETS[platform as PlatformPreset];
  87. const aspectKey = preset.aspect === "16:9" ? "landscape" : "portrait";
  88. return `${template}-${aspectKey}`;
  89. }