1111
1212import { createClient , type SanityClient } from 'next-sanity' ;
1313import { apiVersion , dataset , projectId } from '@/sanity/lib/api' ;
14+ import imageUrlBuilder from '@sanity/image-url' ;
1415import { generateSpeechFromScript } from '@/lib/services/elevenlabs' ;
1516import { generatePerSceneAudio } from '@/lib/services/elevenlabs' ;
1617import type { WordTimestamp } from '@/lib/utils/audio-timestamps' ;
@@ -50,6 +51,12 @@ interface AutomatedVideoDocument {
5051 videoUrl ?: string ;
5152 shortUrl ?: string ;
5253 flaggedReason ?: string ;
54+ infographics ?: Array < {
55+ asset : { _ref : string ; _type : string } ;
56+ alt ?: string ;
57+ caption ?: string ;
58+ } > ;
59+ researchData ?: string ; // JSON string
5360}
5461
5562interface SponsorLeadDocument {
@@ -58,6 +65,61 @@ interface SponsorLeadDocument {
5865 contactName ?: string ;
5966}
6067
68+ // --- Infographic URL Extraction ---
69+
70+ /**
71+ * Extract infographic CDN URLs from the Sanity document.
72+ *
73+ * Priority:
74+ * 1. `doc.infographics[]` — Sanity image assets resolved via @sanity/image-url
75+ * 2. `doc.researchData` — JSON string with `infographicUrls: string[]` (backward compat)
76+ *
77+ * Never throws — returns empty array on failure so the pipeline continues
78+ * with Pexels B-roll fallback.
79+ */
80+ function getInfographicUrls ( doc : AutomatedVideoDocument ) : string [ ] {
81+ try {
82+ // Primary: resolve Sanity image assets to CDN URLs
83+ if ( doc . infographics ?. length ) {
84+ const builder = imageUrlBuilder ( { projectId, dataset } ) ;
85+ const urls = doc . infographics
86+ . map ( ( img ) => {
87+ try {
88+ return builder . image ( img . asset ) . url ( ) ;
89+ } catch {
90+ return null ;
91+ }
92+ } )
93+ . filter ( ( url ) : url is string => ! ! url ) ;
94+
95+ if ( urls . length > 0 ) {
96+ console . log ( `[VIDEO-PIPELINE] Resolved ${ urls . length } infographic URL(s) from Sanity image assets` ) ;
97+ return urls ;
98+ }
99+ }
100+
101+ // Fallback: parse researchData JSON for infographicUrls
102+ if ( doc . researchData ) {
103+ const parsed = JSON . parse ( doc . researchData ) ;
104+ if ( Array . isArray ( parsed ?. infographicUrls ) && parsed . infographicUrls . length > 0 ) {
105+ const urls = parsed . infographicUrls . filter (
106+ ( u : unknown ) : u is string => typeof u === 'string' && u . length > 0
107+ ) ;
108+ if ( urls . length > 0 ) {
109+ console . log ( `[VIDEO-PIPELINE] Resolved ${ urls . length } infographic URL(s) from researchData fallback` ) ;
110+ return urls ;
111+ }
112+ }
113+ }
114+ } catch ( err ) {
115+ console . warn (
116+ `[VIDEO-PIPELINE] Failed to extract infographic URLs (non-fatal): ${ err instanceof Error ? err . message : String ( err ) } `
117+ ) ;
118+ }
119+
120+ return [ ] ;
121+ }
122+
61123// --- Sanity Write Client ---
62124
63125function getSanityWriteClient ( ) : SanityClient {
@@ -132,6 +194,12 @@ export async function processVideoProduction(documentId: string): Promise<void>
132194 `[VIDEO-PIPELINE] Script validated: ${ script . scenes . length } scenes, hook="${ script . hook . substring ( 0 , 50 ) } ..."`
133195 ) ;
134196
197+ // Step 2.5: Extract infographic URLs from Sanity doc
198+ const infographicUrls = getInfographicUrls ( doc ) ;
199+ if ( infographicUrls . length > 0 ) {
200+ console . log ( `[VIDEO-PIPELINE] ${ infographicUrls . length } infographic URL(s) will be distributed across scenes` ) ;
201+ }
202+
135203 // Step 3: Update status to audio_gen
136204 console . log ( `[VIDEO-PIPELINE] Updating status to "audio_gen"` ) ;
137205 await updateStatus ( client , documentId , { status : 'audio_gen' } ) ;
@@ -243,6 +311,9 @@ export async function processVideoProduction(documentId: string): Promise<void>
243311 scenes : script . scenes . map ( ( s , i ) => ( {
244312 ...s ,
245313 wordTimestamps : sceneWordTimestamps [ i ] ,
314+ ...( infographicUrls . length > 0
315+ ? { infographicUrl : infographicUrls [ i % infographicUrls . length ] }
316+ : { } ) ,
246317 } ) ) ,
247318 cta : script . cta ,
248319 } ,
0 commit comments