( videoId: string, )
| 364 | } |
| 365 | |
| 366 | async function waitForProcessingCompletion( |
| 367 | videoId: string, |
| 368 | ): Promise<MediaServerProcessResult> { |
| 369 | let lastStatus = "processing"; |
| 370 | |
| 371 | for ( |
| 372 | let attempt = 0; |
| 373 | attempt < MEDIA_SERVER_COMPLETION_MAX_ATTEMPTS; |
| 374 | attempt++ |
| 375 | ) { |
| 376 | await waitForRetry(MEDIA_SERVER_COMPLETION_POLL_INTERVAL_MS); |
| 377 | |
| 378 | const [upload] = await db() |
| 379 | .select({ |
| 380 | phase: videoUploads.phase, |
| 381 | processingProgress: videoUploads.processingProgress, |
| 382 | processingMessage: videoUploads.processingMessage, |
| 383 | processingError: videoUploads.processingError, |
| 384 | }) |
| 385 | .from(videoUploads) |
| 386 | .where(eq(videoUploads.videoId, videoId as Video.VideoId)); |
| 387 | |
| 388 | if (!upload || upload.phase === "complete") { |
| 389 | const metadata = await getCompletedMetadata(videoId); |
| 390 | if (!metadata) { |
| 391 | throw new Error("Processing completed but video metadata is missing"); |
| 392 | } |
| 393 | |
| 394 | return { metadata }; |
| 395 | } |
| 396 | |
| 397 | if (upload.processingError) { |
| 398 | throw new Error(upload.processingError); |
| 399 | } |
| 400 | |
| 401 | if (upload.phase === "error") { |
| 402 | throw new Error(upload.processingMessage || "Video processing failed"); |
| 403 | } |
| 404 | |
| 405 | lastStatus = [ |
| 406 | upload.phase, |
| 407 | typeof upload.processingProgress === "number" |
| 408 | ? `${upload.processingProgress}%` |
| 409 | : null, |
| 410 | upload.processingMessage, |
| 411 | ] |
| 412 | .filter(Boolean) |
| 413 | .join(" "); |
| 414 | } |
| 415 | |
| 416 | throw new Error(`Video processing timed out while ${lastStatus}`); |
| 417 | } |
| 418 | |
| 419 | async function saveMetadataAndComplete( |
| 420 | videoId: string, |
no test coverage detected