import type { VideoDocument, AssetManifest, RenderProps, RenderScene, PlatformPreset, TemplateType, } from "@pipeline/shared"; import { PLATFORM_PRESETS } from "@pipeline/shared"; import { basename } from "node:path"; import { segmentImageRefs, resolveGithubImages } from "./github-images.js"; /** * Project a document into the render-time `RenderProps` for one platform: * resolve asset filenames for staticFile() and carry each segment's AUDIO * duration. No frame/size computation here — the TEMPLATE computes the visual * timeline (it may extend beyond audio for silent padding, holds, etc.), and * the Remotion Composition owns fps/width/height/durationInFrames. */ export function composeRenderProps( doc: VideoDocument, assets: AssetManifest, platform: PlatformPreset, template: TemplateType, channelName = "Pipeline" ): RenderProps { const scenes: RenderScene[] = doc.data.map((seg, i) => { const backgroundAsset = assets.backgrounds[i] ? { id: assets.backgrounds[i].id, filename: basename(assets.backgrounds[i].localPath) } : undefined; const images = segmentImageRefs(seg) .map((img) => { const resolved = assets.images.find( (a) => (img.path && a.path === img.path) || (img.url && a.url === img.url) || (img.query && a.query === img.query) || (img.repoSocialPreview && a.repoSocialPreview === img.repoSocialPreview) ); return resolved ? { filename: basename(resolved.localPath), url: img.url, query: img.query } : undefined; }) .filter((x): x is NonNullable => !!x); // Resolve the per-template extension (github-trending images → filenames). const extension = seg.extension?.type === "github-trending" ? { type: "github-trending" as const, repo: seg.extension.repo, highlights: seg.extension.highlights, intro: seg.extension.intro, review: seg.extension.review, images: resolveGithubImages(seg.extension, assets), } : undefined; return { id: seg.id, kind: seg.kind, index: seg.index, title: seg.title, desc: seg.desc, captionOrigin: seg.captionOrigin, caption: seg.caption, cardList: seg.cardList, menu: seg.menu, images, layoutHint: seg.layoutHint, extension, duration: seg.duration, // audio seconds (template computes visual frames) audioFilename: seg.captionAudioFileUrl ? basename(seg.captionAudioFileUrl) : "", backgroundAsset, }; }); return { scenes, template, platform, title: doc.meta?.title || "", subtitle: doc.meta?.subtitle || "", channelName, trendSummary: doc.meta?.trendSummary, globalStyle: { tone: "formal", pace: "normal" }, }; } /** Composition id the Remotion root registers (Root.tsx uses `${template}-${landscape|portrait}`). */ export function compositionIdFor(template: TemplateType, platform: string): string { const preset = PLATFORM_PRESETS[platform as PlatformPreset]; const aspectKey = preset.aspect === "16:9" ? "landscape" : "portrait"; return `${template}-${aspectKey}`; }