Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/services/remotion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface RenderInput {
bRollKeywords?: string[];
sceneNumber?: number;
durationEstimate?: number;
infographicUrl?: string;
}>;
cta: string;
};
Expand Down Expand Up @@ -117,6 +118,7 @@ function mapInputProps(input: RenderInput): Record<string, unknown> {
sceneNumber: s.sceneNumber,
durationEstimate: s.durationEstimate,
bRollUrl: input.bRollUrls[i],
...(s.infographicUrl ? { infographicUrl: s.infographicUrl } : {}),
})),
cta: input.script.cta,
sponsor: input.sponsor,
Expand Down
71 changes: 71 additions & 0 deletions lib/services/video-pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import { createClient, type SanityClient } from 'next-sanity';
import { apiVersion, dataset, projectId } from '@/sanity/lib/api';
import imageUrlBuilder from '@sanity/image-url';
import { generateSpeechFromScript } from '@/lib/services/elevenlabs';
import { generatePerSceneAudio } from '@/lib/services/elevenlabs';
import type { WordTimestamp } from '@/lib/utils/audio-timestamps';
Expand Down Expand Up @@ -50,6 +51,12 @@ interface AutomatedVideoDocument {
videoUrl?: string;
shortUrl?: string;
flaggedReason?: string;
infographics?: Array<{
asset: { _ref: string; _type: string };
alt?: string;
caption?: string;
}>;
researchData?: string; // JSON string
}

interface SponsorLeadDocument {
Expand All @@ -58,6 +65,61 @@ interface SponsorLeadDocument {
contactName?: string;
}

// --- Infographic URL Extraction ---

/**
* Extract infographic CDN URLs from the Sanity document.
*
* Priority:
* 1. `doc.infographics[]` — Sanity image assets resolved via @sanity/image-url
* 2. `doc.researchData` — JSON string with `infographicUrls: string[]` (backward compat)
*
* Never throws — returns empty array on failure so the pipeline continues
* with Pexels B-roll fallback.
*/
function getInfographicUrls(doc: AutomatedVideoDocument): string[] {
try {
// Primary: resolve Sanity image assets to CDN URLs
if (doc.infographics?.length) {
const builder = imageUrlBuilder({ projectId, dataset });
const urls = doc.infographics
.map((img) => {
try {
return builder.image(img.asset).url();
} catch {
return null;
}
})
.filter((url): url is string => !!url);

if (urls.length > 0) {
console.log(`[VIDEO-PIPELINE] Resolved ${urls.length} infographic URL(s) from Sanity image assets`);
return urls;
}
}

// Fallback: parse researchData JSON for infographicUrls
if (doc.researchData) {
const parsed = JSON.parse(doc.researchData);
if (Array.isArray(parsed?.infographicUrls) && parsed.infographicUrls.length > 0) {
const urls = parsed.infographicUrls.filter(
(u: unknown): u is string => typeof u === 'string' && u.length > 0
);
if (urls.length > 0) {
console.log(`[VIDEO-PIPELINE] Resolved ${urls.length} infographic URL(s) from researchData fallback`);
return urls;
}
}
}
} catch (err) {
console.warn(
`[VIDEO-PIPELINE] Failed to extract infographic URLs (non-fatal): ${err instanceof Error ? err.message : String(err)}`
);
}

return [];
}

// --- Sanity Write Client ---

function getSanityWriteClient(): SanityClient {
Expand Down Expand Up @@ -132,6 +194,12 @@ export async function processVideoProduction(documentId: string): Promise<void>
`[VIDEO-PIPELINE] Script validated: ${script.scenes.length} scenes, hook="${script.hook.substring(0, 50)}..."`
);

// Step 2.5: Extract infographic URLs from Sanity doc
const infographicUrls = getInfographicUrls(doc);
if (infographicUrls.length > 0) {
console.log(`[VIDEO-PIPELINE] ${infographicUrls.length} infographic URL(s) will be distributed across scenes`);
}

// Step 3: Update status to audio_gen
console.log(`[VIDEO-PIPELINE] Updating status to "audio_gen"`);
await updateStatus(client, documentId, { status: 'audio_gen' });
Expand Down Expand Up @@ -243,6 +311,9 @@ export async function processVideoProduction(documentId: string): Promise<void>
scenes: script.scenes.map((s, i) => ({
...s,
wordTimestamps: sceneWordTimestamps[i],
...(infographicUrls.length > 0
? { infographicUrl: infographicUrls[i % infographicUrls.length] }
: {}),
})),
cta: script.cta,
},
Expand Down
Loading