1+ "use server" ;
2+
3+ import { getCurrentUser } from "@cap/database/auth/session" ;
4+ import { db } from "@cap/database" ;
5+ import { videos , users } from "@cap/database/schema" ;
6+ import { VideoMetadata } from "@cap/database/types" ;
7+ import { eq } from "drizzle-orm" ;
8+ import { generateAiMetadata } from "./generate-ai-metadata" ;
9+ import { transcribeVideo } from "./transcribe" ;
10+ import { isAiGenerationEnabled } from "@/utils/flags" ;
11+
12+ const MAX_AI_PROCESSING_TIME = 10 * 60 * 1000 ;
13+
14+ export interface VideoStatusResult {
15+ transcriptionStatus : "PROCESSING" | "COMPLETE" | "ERROR" | null ;
16+ aiProcessing : boolean ;
17+ aiTitle : string | null ;
18+ summary : string | null ;
19+ chapters : { title : string ; start : number } [ ] | null ;
20+ generationError : string | null ;
21+ error ?: string ;
22+ }
23+
24+ export async function getVideoStatus ( videoId : string ) : Promise < VideoStatusResult > {
25+ const user = await getCurrentUser ( ) ;
26+
27+ if ( ! user ) {
28+ throw new Error ( "Authentication required" ) ;
29+ }
30+
31+ if ( ! videoId ) {
32+ throw new Error ( "Video ID not provided" ) ;
33+ }
34+
35+ const result = await db ( ) . select ( ) . from ( videos ) . where ( eq ( videos . id , videoId ) ) ;
36+ if ( result . length === 0 || ! result [ 0 ] ) {
37+ throw new Error ( "Video not found" ) ;
38+ }
39+
40+ const video = result [ 0 ] ;
41+ const metadata : VideoMetadata = ( video . metadata as VideoMetadata ) || { } ;
42+
43+ if ( ! video . transcriptionStatus ) {
44+ console . log ( `[Get Status] Transcription not started for video ${ videoId } , triggering transcription` ) ;
45+ try {
46+ transcribeVideo ( videoId , video . ownerId ) . catch ( error => {
47+ console . error ( `[Get Status] Error starting transcription for video ${ videoId } :` , error ) ;
48+ } ) ;
49+
50+ return {
51+ transcriptionStatus : "PROCESSING" ,
52+ aiProcessing : false ,
53+ aiTitle : metadata . aiTitle || null ,
54+ summary : metadata . summary || null ,
55+ chapters : metadata . chapters || null ,
56+ generationError : metadata . generationError || null ,
57+ } ;
58+ } catch ( error ) {
59+ console . error ( `[Get Status] Error triggering transcription for video ${ videoId } :` , error ) ;
60+ return {
61+ transcriptionStatus : "ERROR" ,
62+ aiProcessing : false ,
63+ aiTitle : metadata . aiTitle || null ,
64+ summary : metadata . summary || null ,
65+ chapters : metadata . chapters || null ,
66+ generationError : metadata . generationError || null ,
67+ error : "Failed to start transcription"
68+ } ;
69+ }
70+ }
71+
72+ if ( video . transcriptionStatus === "ERROR" ) {
73+ return {
74+ transcriptionStatus : "ERROR" ,
75+ aiProcessing : false ,
76+ aiTitle : metadata . aiTitle || null ,
77+ summary : metadata . summary || null ,
78+ chapters : metadata . chapters || null ,
79+ generationError : metadata . generationError || null ,
80+ error : "Transcription failed"
81+ } ;
82+ }
83+
84+ if ( metadata . aiProcessing ) {
85+ const updatedAtTime = new Date ( video . updatedAt ) . getTime ( ) ;
86+ const currentTime = new Date ( ) . getTime ( ) ;
87+
88+ if ( currentTime - updatedAtTime > MAX_AI_PROCESSING_TIME ) {
89+ console . log ( `[Get Status] AI processing appears stuck for video ${ videoId } (${ Math . round ( ( currentTime - updatedAtTime ) / 60000 ) } minutes), resetting flag` ) ;
90+
91+ await db ( )
92+ . update ( videos )
93+ . set ( {
94+ metadata : {
95+ ...metadata ,
96+ aiProcessing : false ,
97+ generationError : "AI processing timed out and was reset"
98+ }
99+ } )
100+ . where ( eq ( videos . id , videoId ) ) ;
101+
102+ const updatedResult = await db ( ) . select ( ) . from ( videos ) . where ( eq ( videos . id , videoId ) ) ;
103+ if ( updatedResult . length > 0 && updatedResult [ 0 ] ) {
104+ const updatedVideo = updatedResult [ 0 ] ;
105+ const updatedMetadata = updatedVideo . metadata as VideoMetadata || { } ;
106+
107+ return {
108+ transcriptionStatus : ( updatedVideo . transcriptionStatus as "PROCESSING" | "COMPLETE" | "ERROR" ) || null ,
109+ aiProcessing : false ,
110+ aiTitle : updatedMetadata . aiTitle || null ,
111+ summary : updatedMetadata . summary || null ,
112+ chapters : updatedMetadata . chapters || null ,
113+ generationError : updatedMetadata . generationError || null ,
114+ error : "AI processing timed out and was reset"
115+ } ;
116+ }
117+ }
118+ }
119+
120+ if (
121+ video . transcriptionStatus === "COMPLETE" &&
122+ ! metadata . aiProcessing &&
123+ ! metadata . summary &&
124+ ! metadata . chapters &&
125+ ! metadata . generationError
126+ ) {
127+ console . log ( `[Get Status] Transcription complete but no AI data, checking feature flag for video owner ${ video . ownerId } ` ) ;
128+
129+ const videoOwnerQuery = await db ( )
130+ . select ( {
131+ email : users . email ,
132+ stripeSubscriptionStatus : users . stripeSubscriptionStatus
133+ } )
134+ . from ( users )
135+ . where ( eq ( users . id , video . ownerId ) )
136+ . limit ( 1 ) ;
137+
138+ if ( videoOwnerQuery . length > 0 && videoOwnerQuery [ 0 ] && ( await isAiGenerationEnabled ( videoOwnerQuery [ 0 ] ) ) ) {
139+ console . log ( `[Get Status] Feature flag enabled, triggering AI generation for video ${ videoId } ` ) ;
140+
141+ ( async ( ) => {
142+ try {
143+ console . log ( `[Get Status] Starting AI metadata generation for video ${ videoId } ` ) ;
144+ await generateAiMetadata ( videoId , video . ownerId ) ;
145+ console . log ( `[Get Status] AI metadata generation completed for video ${ videoId } ` ) ;
146+ } catch ( error ) {
147+ console . error ( `[Get Status] Error generating AI metadata for video ${ videoId } :` , error ) ;
148+
149+ try {
150+ const currentVideo = await db ( ) . select ( ) . from ( videos ) . where ( eq ( videos . id , videoId ) ) ;
151+ if ( currentVideo . length > 0 && currentVideo [ 0 ] ) {
152+ const currentMetadata = ( currentVideo [ 0 ] . metadata as VideoMetadata ) || { } ;
153+ await db ( )
154+ . update ( videos )
155+ . set ( {
156+ metadata : {
157+ ...currentMetadata ,
158+ aiProcessing : false ,
159+ generationError : error instanceof Error ? error . message : String ( error )
160+ }
161+ } )
162+ . where ( eq ( videos . id , videoId ) ) ;
163+ }
164+ } catch ( resetError ) {
165+ console . error ( `[Get Status] Failed to reset AI processing flag for video ${ videoId } :` , resetError ) ;
166+ }
167+ }
168+ } ) ( ) ;
169+
170+ return {
171+ transcriptionStatus : ( video . transcriptionStatus as "PROCESSING" | "COMPLETE" | "ERROR" ) || null ,
172+ aiProcessing : true ,
173+ aiTitle : metadata . aiTitle || null ,
174+ summary : metadata . summary || null ,
175+ chapters : metadata . chapters || null ,
176+ generationError : metadata . generationError || null ,
177+ } ;
178+ } else {
179+ const videoOwner = videoOwnerQuery [ 0 ] ;
180+ console . log ( `[Get Status] AI generation feature disabled for video owner ${ video . ownerId } (email: ${ videoOwner ?. email } , pro: ${ videoOwner ?. stripeSubscriptionStatus } )` ) ;
181+ }
182+ }
183+
184+ return {
185+ transcriptionStatus : ( video . transcriptionStatus as "PROCESSING" | "COMPLETE" | "ERROR" ) || null ,
186+ aiProcessing : metadata . aiProcessing || false ,
187+ aiTitle : metadata . aiTitle || null ,
188+ summary : metadata . summary || null ,
189+ chapters : metadata . chapters || null ,
190+ generationError : metadata . generationError || null ,
191+ } ;
192+ }
0 commit comments