@stack0/sdk 0.5.16 → 0.5.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -483,14 +483,17 @@ console.log(`Progress: ${status.progress}%`);
483
483
  const urls = await stack0.cdn.getStreamingUrls('asset-id');
484
484
  console.log(urls.hlsUrl);
485
485
 
486
- // Generate thumbnails
487
- const thumb = await stack0.cdn.getThumbnail({
486
+ // Get a thumbnail at a timestamp (generated on miss; waits until ready)
487
+ const thumb = await stack0.cdn.getThumbnailAndWait({
488
488
  assetId: 'video-asset-id',
489
489
  timestamp: 10.5,
490
490
  width: 320,
491
491
  format: 'webp',
492
492
  });
493
493
 
494
+ // Non-blocking variant: returns { status: 'pending', url: null } while generating
495
+ const maybe = await stack0.cdn.getThumbnail({ assetId: 'video-asset-id', timestamp: 0 });
496
+
494
497
  // Extract audio
495
498
  const { jobId } = await stack0.cdn.extractAudio({
496
499
  projectSlug: 'my-project',
@@ -534,6 +537,28 @@ const mergeJob = await stack0.cdn.createMergeJob({
534
537
  audioTrack: { assetId: 'music-id', loop: true, fadeIn: 2, fadeOut: 3 },
535
538
  output: { format: 'mp4', quality: '1080p', filename: 'final.mp4' },
536
539
  });
540
+
541
+ // Composite a screen recording onto a held phone (screen keyed #F500FA):
542
+ // the keyed background sits on top, so fingers occlude the inset recording
543
+ const composited = await stack0.cdn.createMergeJob({
544
+ projectSlug: 'my-project',
545
+ inputs: [
546
+ {
547
+ assetId: 'app-screen-recording',
548
+ composite: {
549
+ backgroundAssetId: 'holding-phone-shot',
550
+ quad: {
551
+ topLeft: { x: 320, y: 480 },
552
+ topRight: { x: 760, y: 500 },
553
+ bottomLeft: { x: 300, y: 1400 },
554
+ bottomRight: { x: 740, y: 1440 },
555
+ },
556
+ fit: 'cover',
557
+ chromaKey: { color: '#F500FA', similarity: 0.34, blend: 0.06 },
558
+ },
559
+ },
560
+ ],
561
+ });
537
562
  ```
538
563
 
539
564
  ### Private Files
@@ -335,10 +335,16 @@ interface ThumbnailRequest {
335
335
  format?: "jpg" | "png" | "webp";
336
336
  }
337
337
  interface ThumbnailResponse {
338
- url: string;
338
+ id: string | null;
339
+ assetId: string;
339
340
  timestamp: number;
340
- width: number;
341
- height: number;
341
+ /** CDN URL of the frame; null while status is "pending" */
342
+ url: string | null;
343
+ width: number | null;
344
+ height: number | null;
345
+ format: string;
346
+ /** "ready" when the frame exists; "pending" while generation is queued/in flight */
347
+ status: "ready" | "pending";
342
348
  }
343
349
  interface RegenerateThumbnailRequest {
344
350
  assetId: string;
@@ -693,6 +699,63 @@ interface TextOverlay {
693
699
  /** Stroke/outline configuration for better visibility */
694
700
  stroke?: TextOverlayStroke;
695
701
  }
702
+ /** A point in background pixel coordinates */
703
+ interface CompositePoint {
704
+ x: number;
705
+ y: number;
706
+ }
707
+ /**
708
+ * Perspective destination quad: where the overlay's four corners land on the
709
+ * background, in background pixels. Use for corner-pinning a clip onto an
710
+ * angled surface like a phone screen in a held-phone shot.
711
+ */
712
+ interface CompositeQuad {
713
+ topLeft: CompositePoint;
714
+ topRight: CompositePoint;
715
+ bottomLeft: CompositePoint;
716
+ bottomRight: CompositePoint;
717
+ }
718
+ /** Axis-aligned destination rectangle in background pixels (no perspective) */
719
+ interface CompositeRect {
720
+ x: number;
721
+ y: number;
722
+ width: number;
723
+ height: number;
724
+ }
725
+ /**
726
+ * Chroma key applied to the background: the keyed color becomes transparent so
727
+ * the overlay shows through only there, and real foreground (fingers, hands)
728
+ * occludes the overlay.
729
+ */
730
+ interface CompositeChromaKey {
731
+ /** Key color in hex format (e.g., "#F500FA") */
732
+ color: string;
733
+ /** Color distance tolerance 0.01-1 (default: 0.3) */
734
+ similarity?: number;
735
+ /** Edge blend 0-1 (default: 0.1) */
736
+ blend?: number;
737
+ }
738
+ /**
739
+ * Composite configuration: place this input (the overlay) into a region of a
740
+ * background asset. The background can be an image or video. The keyed or
741
+ * masked background is layered on top of the placed overlay, so foreground in
742
+ * the background shot occludes the inset — ideal for "POV holding my phone"
743
+ * style UGC where an app recording is pinned onto the phone's (keyed) screen.
744
+ */
745
+ interface CompositeConfig {
746
+ /** Asset ID of the background image or video */
747
+ backgroundAssetId: string;
748
+ /** Perspective corner-pin destination (requires chromaKey or maskAssetId) */
749
+ quad?: CompositeQuad;
750
+ /** Axis-aligned destination rectangle (simple PiP when no key/mask) */
751
+ rect?: CompositeRect;
752
+ /** How the overlay fills the destination region's aspect ratio (default: "cover") */
753
+ fit?: "cover" | "contain" | "fill";
754
+ /** Chroma key applied to the background */
755
+ chromaKey?: CompositeChromaKey;
756
+ /** Grayscale mask image asset: white keeps the background, black reveals the overlay */
757
+ maskAssetId?: string;
758
+ }
696
759
  /**
697
760
  * A single input item for the merge operation.
698
761
  * Can be a video, image, or audio file.
@@ -708,6 +771,8 @@ interface MergeInputItem {
708
771
  endTime?: number;
709
772
  /** Text overlay/caption configuration */
710
773
  textOverlay?: TextOverlay;
774
+ /** Composite this input into a region of a background asset */
775
+ composite?: CompositeConfig;
711
776
  }
712
777
  /**
713
778
  * Audio track overlay configuration for merge jobs.
@@ -1309,7 +1374,12 @@ declare class CDN {
1309
1374
  */
1310
1375
  getStreamingUrls(assetId: string): Promise<StreamingUrls>;
1311
1376
  /**
1312
- * Generate a thumbnail from a video at a specific timestamp
1377
+ * Get the thumbnail at a specific timestamp, generating it on miss
1378
+ *
1379
+ * If no thumbnail exists at that timestamp yet, generation is queued
1380
+ * automatically and a response with `status: "pending"` (null url) is
1381
+ * returned. Poll until `status` is "ready", or use
1382
+ * {@link getThumbnailAndWait} to do that for you.
1313
1383
  *
1314
1384
  * @example
1315
1385
  * ```typescript
@@ -1319,24 +1389,48 @@ declare class CDN {
1319
1389
  * width: 320,
1320
1390
  * format: 'webp',
1321
1391
  * });
1322
- * console.log(`Thumbnail URL: ${thumbnail.url}`);
1392
+ * if (thumbnail.status === 'ready') {
1393
+ * console.log(`Thumbnail URL: ${thumbnail.url}`);
1394
+ * }
1323
1395
  * ```
1324
1396
  */
1325
1397
  getThumbnail(request: ThumbnailRequest): Promise<ThumbnailResponse>;
1398
+ /**
1399
+ * Get the thumbnail at a specific timestamp, waiting for generation to complete
1400
+ *
1401
+ * Calls {@link getThumbnail} and polls until the thumbnail is ready.
1402
+ *
1403
+ * @example
1404
+ * ```typescript
1405
+ * const thumbnail = await cdn.getThumbnailAndWait({
1406
+ * assetId: 'video-asset-id',
1407
+ * timestamp: 3.5,
1408
+ * });
1409
+ * console.log(`Thumbnail URL: ${thumbnail.url}`);
1410
+ * ```
1411
+ */
1412
+ getThumbnailAndWait(request: ThumbnailRequest, options?: {
1413
+ pollInterval?: number;
1414
+ timeout?: number;
1415
+ }): Promise<ThumbnailResponse>;
1326
1416
  /**
1327
1417
  * Regenerate a thumbnail for a video (force regeneration even if one exists)
1328
1418
  *
1329
- * Useful for retrying failed thumbnail generation or regenerating with different settings.
1419
+ * Useful for retrying failed thumbnail generation or regenerating with
1420
+ * different settings. Regeneration is async: poll {@link getThumbnail} (or
1421
+ * use {@link getThumbnailAndWait}) at the same timestamp until the new
1422
+ * frame is ready.
1330
1423
  *
1331
1424
  * @example
1332
1425
  * ```typescript
1333
- * const result = await cdn.regenerateThumbnail({
1426
+ * await cdn.regenerateThumbnail({
1334
1427
  * assetId: 'video-asset-id',
1335
1428
  * timestamp: 5, // 5 seconds into the video
1336
1429
  * width: 1280,
1337
1430
  * format: 'jpg',
1338
1431
  * });
1339
- * console.log(`Thumbnail regeneration queued: ${result.status}`);
1432
+ * const thumbnail = await cdn.getThumbnailAndWait({ assetId: 'video-asset-id', timestamp: 5 });
1433
+ * console.log(`Thumbnail URL: ${thumbnail.url}`);
1340
1434
  * ```
1341
1435
  */
1342
1436
  regenerateThumbnail(request: RegenerateThumbnailRequest): Promise<RegenerateThumbnailResponse>;
@@ -1716,6 +1810,33 @@ declare class CDN {
1716
1810
  * });
1717
1811
  * console.log(`Merge job started: ${job.id}`);
1718
1812
  * ```
1813
+ *
1814
+ * @example Composite a screen recording onto a held phone ("POV holding my phone"):
1815
+ * ```typescript
1816
+ * const job = await cdn.createMergeJob({
1817
+ * projectSlug: 'my-project',
1818
+ * inputs: [
1819
+ * { assetId: 'reaction-clip-id' },
1820
+ * {
1821
+ * assetId: 'app-recording-id', // the overlay clip
1822
+ * composite: {
1823
+ * backgroundAssetId: 'holding-phone-shot-id', // image or video
1824
+ * // Corner-pin onto the angled screen (background pixel coords, TL/TR/BL/BR)
1825
+ * quad: {
1826
+ * topLeft: { x: 320, y: 480 },
1827
+ * topRight: { x: 760, y: 500 },
1828
+ * bottomLeft: { x: 300, y: 1400 },
1829
+ * bottomRight: { x: 740, y: 1440 },
1830
+ * },
1831
+ * fit: 'cover',
1832
+ * // Screen area painted #F500FA in the background is keyed out, so the
1833
+ * // recording shows through and fingers over the screen occlude it
1834
+ * chromaKey: { color: '#F500FA', similarity: 0.34, blend: 0.06 },
1835
+ * },
1836
+ * },
1837
+ * ],
1838
+ * });
1839
+ * ```
1719
1840
  */
1720
1841
  createMergeJob(request: CreateMergeJobRequest): Promise<MergeJob>;
1721
1842
  /**
@@ -1874,4 +1995,4 @@ declare class CDN {
1874
1995
  private convertImportFileDates;
1875
1996
  }
1876
1997
 
1877
- export { type Asset, type AssetStatus, type AssetType, type AudioTrackInput, type BundleDownloadUrlRequest, type BundleDownloadUrlResponse, type BundleStatus, CDN, type CancelImportResponse, type CancelMergeJobRequest, type CdnEnvironment, type CdnStorageBreakdownItem, type CdnStorageBreakdownRequest, type CdnStorageBreakdownResponse, type CdnUsageDataPoint, type CdnUsageHistoryRequest, type CdnUsageHistoryResponse, type CdnUsageRequest, type CdnUsageResponse, type ConfirmUploadRequest, type ConfirmUploadResponse, type CreateBundleRequest, type CreateBundleResponse, type CreateFolderRequest, type CreateImportRequest, type CreateImportResponse, type CreateMergeJobRequest, type DeleteAssetRequest, type DeleteAssetsRequest, type DeleteAssetsResponse, type DownloadBundle, type ExtractAudioRequest, type ExtractAudioResponse, type Folder, type FolderListItem, type FolderTreeNode, type GenerateGifRequest, type GetAssetRequest, type GetFolderByPathRequest, type GetFolderRequest, type GetFolderTreeRequest, type GetMergeJobRequest, type GifStatus, type ImageVideoWatermarkOptions, type ImageWatermarkConfig, type ImageWatermarkPosition, type ImageWatermarkSizingMode, type ImportAuthType, type ImportError, type ImportFile, type ImportFileStatus, type ImportJob, type ImportJobStatus, type ImportJobSummary, type ImportPathMode, type ListAssetsRequest, type ListAssetsResponse, type ListBundlesRequest, type ListBundlesResponse, type ListFoldersRequest, type ListFoldersResponse, type ListGifsRequest, type ListImportFilesRequest, type ListImportFilesResponse, type ListImportsRequest, type ListImportsResponse, type ListJobsRequest, type ListJobsResponse, type ListMergeJobsRequest, type ListMergeJobsResponse, type ListPrivateFilesRequest, type ListPrivateFilesResponse, type ListThumbnailsRequest, type ListThumbnailsResponse, type MergeAspectRatio, type MergeInputItem, type MergeJob, type MergeJobWithOutput, type MergeOutputConfig, type MergeOutputFormat, type MergeQuality, type MergeStatus, type MoveAssetsRequest, type MoveAssetsResponse, type MoveFolderRequest, type MoveFolderResponse, type MovePrivateFilesRequest, type MovePrivateFilesResponse, type PrivateDownloadUrlRequest, type PrivateDownloadUrlResponse, type PrivateFile, type PrivateFileStatus, type PrivateUploadUrlRequest, type PrivateUploadUrlResponse, type RegenerateThumbnailRequest, type RegenerateThumbnailResponse, type RetryImportResponse, type StreamingUrls, type TextOverlay, type TextOverlayShadow, type TextOverlayStroke, type TextVideoWatermarkOptions, type ThumbnailRequest, type ThumbnailResponse, type TranscodeJob, type TranscodeVideoRequest, type TranscodingStatus, type TransformOptions, type TrimOptions, type UpdateAssetRequest, type UpdateFolderRequest, type UpdatePrivateFileRequest, type UploadFromUrlRequest, type UploadUrlRequest, type UploadUrlResponse, type VideoCodec, type VideoGif, type VideoOutputFormat, type VideoQuality, type VideoThumbnail, type VideoVariant, type WatermarkOptions };
1998
+ export { type Asset, type AssetStatus, type AssetType, type AudioTrackInput, type BundleDownloadUrlRequest, type BundleDownloadUrlResponse, type BundleStatus, CDN, type CancelImportResponse, type CancelMergeJobRequest, type CdnEnvironment, type CdnStorageBreakdownItem, type CdnStorageBreakdownRequest, type CdnStorageBreakdownResponse, type CdnUsageDataPoint, type CdnUsageHistoryRequest, type CdnUsageHistoryResponse, type CdnUsageRequest, type CdnUsageResponse, type CompositeChromaKey, type CompositeConfig, type CompositePoint, type CompositeQuad, type CompositeRect, type ConfirmUploadRequest, type ConfirmUploadResponse, type CreateBundleRequest, type CreateBundleResponse, type CreateFolderRequest, type CreateImportRequest, type CreateImportResponse, type CreateMergeJobRequest, type DeleteAssetRequest, type DeleteAssetsRequest, type DeleteAssetsResponse, type DownloadBundle, type ExtractAudioRequest, type ExtractAudioResponse, type Folder, type FolderListItem, type FolderTreeNode, type GenerateGifRequest, type GetAssetRequest, type GetFolderByPathRequest, type GetFolderRequest, type GetFolderTreeRequest, type GetMergeJobRequest, type GifStatus, type ImageVideoWatermarkOptions, type ImageWatermarkConfig, type ImageWatermarkPosition, type ImageWatermarkSizingMode, type ImportAuthType, type ImportError, type ImportFile, type ImportFileStatus, type ImportJob, type ImportJobStatus, type ImportJobSummary, type ImportPathMode, type ListAssetsRequest, type ListAssetsResponse, type ListBundlesRequest, type ListBundlesResponse, type ListFoldersRequest, type ListFoldersResponse, type ListGifsRequest, type ListImportFilesRequest, type ListImportFilesResponse, type ListImportsRequest, type ListImportsResponse, type ListJobsRequest, type ListJobsResponse, type ListMergeJobsRequest, type ListMergeJobsResponse, type ListPrivateFilesRequest, type ListPrivateFilesResponse, type ListThumbnailsRequest, type ListThumbnailsResponse, type MergeAspectRatio, type MergeInputItem, type MergeJob, type MergeJobWithOutput, type MergeOutputConfig, type MergeOutputFormat, type MergeQuality, type MergeStatus, type MoveAssetsRequest, type MoveAssetsResponse, type MoveFolderRequest, type MoveFolderResponse, type MovePrivateFilesRequest, type MovePrivateFilesResponse, type PrivateDownloadUrlRequest, type PrivateDownloadUrlResponse, type PrivateFile, type PrivateFileStatus, type PrivateUploadUrlRequest, type PrivateUploadUrlResponse, type RegenerateThumbnailRequest, type RegenerateThumbnailResponse, type RetryImportResponse, type StreamingUrls, type TextOverlay, type TextOverlayShadow, type TextOverlayStroke, type TextVideoWatermarkOptions, type ThumbnailRequest, type ThumbnailResponse, type TranscodeJob, type TranscodeVideoRequest, type TranscodingStatus, type TransformOptions, type TrimOptions, type UpdateAssetRequest, type UpdateFolderRequest, type UpdatePrivateFileRequest, type UploadFromUrlRequest, type UploadUrlRequest, type UploadUrlResponse, type VideoCodec, type VideoGif, type VideoOutputFormat, type VideoQuality, type VideoThumbnail, type VideoVariant, type WatermarkOptions };
@@ -335,10 +335,16 @@ interface ThumbnailRequest {
335
335
  format?: "jpg" | "png" | "webp";
336
336
  }
337
337
  interface ThumbnailResponse {
338
- url: string;
338
+ id: string | null;
339
+ assetId: string;
339
340
  timestamp: number;
340
- width: number;
341
- height: number;
341
+ /** CDN URL of the frame; null while status is "pending" */
342
+ url: string | null;
343
+ width: number | null;
344
+ height: number | null;
345
+ format: string;
346
+ /** "ready" when the frame exists; "pending" while generation is queued/in flight */
347
+ status: "ready" | "pending";
342
348
  }
343
349
  interface RegenerateThumbnailRequest {
344
350
  assetId: string;
@@ -693,6 +699,63 @@ interface TextOverlay {
693
699
  /** Stroke/outline configuration for better visibility */
694
700
  stroke?: TextOverlayStroke;
695
701
  }
702
+ /** A point in background pixel coordinates */
703
+ interface CompositePoint {
704
+ x: number;
705
+ y: number;
706
+ }
707
+ /**
708
+ * Perspective destination quad: where the overlay's four corners land on the
709
+ * background, in background pixels. Use for corner-pinning a clip onto an
710
+ * angled surface like a phone screen in a held-phone shot.
711
+ */
712
+ interface CompositeQuad {
713
+ topLeft: CompositePoint;
714
+ topRight: CompositePoint;
715
+ bottomLeft: CompositePoint;
716
+ bottomRight: CompositePoint;
717
+ }
718
+ /** Axis-aligned destination rectangle in background pixels (no perspective) */
719
+ interface CompositeRect {
720
+ x: number;
721
+ y: number;
722
+ width: number;
723
+ height: number;
724
+ }
725
+ /**
726
+ * Chroma key applied to the background: the keyed color becomes transparent so
727
+ * the overlay shows through only there, and real foreground (fingers, hands)
728
+ * occludes the overlay.
729
+ */
730
+ interface CompositeChromaKey {
731
+ /** Key color in hex format (e.g., "#F500FA") */
732
+ color: string;
733
+ /** Color distance tolerance 0.01-1 (default: 0.3) */
734
+ similarity?: number;
735
+ /** Edge blend 0-1 (default: 0.1) */
736
+ blend?: number;
737
+ }
738
+ /**
739
+ * Composite configuration: place this input (the overlay) into a region of a
740
+ * background asset. The background can be an image or video. The keyed or
741
+ * masked background is layered on top of the placed overlay, so foreground in
742
+ * the background shot occludes the inset — ideal for "POV holding my phone"
743
+ * style UGC where an app recording is pinned onto the phone's (keyed) screen.
744
+ */
745
+ interface CompositeConfig {
746
+ /** Asset ID of the background image or video */
747
+ backgroundAssetId: string;
748
+ /** Perspective corner-pin destination (requires chromaKey or maskAssetId) */
749
+ quad?: CompositeQuad;
750
+ /** Axis-aligned destination rectangle (simple PiP when no key/mask) */
751
+ rect?: CompositeRect;
752
+ /** How the overlay fills the destination region's aspect ratio (default: "cover") */
753
+ fit?: "cover" | "contain" | "fill";
754
+ /** Chroma key applied to the background */
755
+ chromaKey?: CompositeChromaKey;
756
+ /** Grayscale mask image asset: white keeps the background, black reveals the overlay */
757
+ maskAssetId?: string;
758
+ }
696
759
  /**
697
760
  * A single input item for the merge operation.
698
761
  * Can be a video, image, or audio file.
@@ -708,6 +771,8 @@ interface MergeInputItem {
708
771
  endTime?: number;
709
772
  /** Text overlay/caption configuration */
710
773
  textOverlay?: TextOverlay;
774
+ /** Composite this input into a region of a background asset */
775
+ composite?: CompositeConfig;
711
776
  }
712
777
  /**
713
778
  * Audio track overlay configuration for merge jobs.
@@ -1309,7 +1374,12 @@ declare class CDN {
1309
1374
  */
1310
1375
  getStreamingUrls(assetId: string): Promise<StreamingUrls>;
1311
1376
  /**
1312
- * Generate a thumbnail from a video at a specific timestamp
1377
+ * Get the thumbnail at a specific timestamp, generating it on miss
1378
+ *
1379
+ * If no thumbnail exists at that timestamp yet, generation is queued
1380
+ * automatically and a response with `status: "pending"` (null url) is
1381
+ * returned. Poll until `status` is "ready", or use
1382
+ * {@link getThumbnailAndWait} to do that for you.
1313
1383
  *
1314
1384
  * @example
1315
1385
  * ```typescript
@@ -1319,24 +1389,48 @@ declare class CDN {
1319
1389
  * width: 320,
1320
1390
  * format: 'webp',
1321
1391
  * });
1322
- * console.log(`Thumbnail URL: ${thumbnail.url}`);
1392
+ * if (thumbnail.status === 'ready') {
1393
+ * console.log(`Thumbnail URL: ${thumbnail.url}`);
1394
+ * }
1323
1395
  * ```
1324
1396
  */
1325
1397
  getThumbnail(request: ThumbnailRequest): Promise<ThumbnailResponse>;
1398
+ /**
1399
+ * Get the thumbnail at a specific timestamp, waiting for generation to complete
1400
+ *
1401
+ * Calls {@link getThumbnail} and polls until the thumbnail is ready.
1402
+ *
1403
+ * @example
1404
+ * ```typescript
1405
+ * const thumbnail = await cdn.getThumbnailAndWait({
1406
+ * assetId: 'video-asset-id',
1407
+ * timestamp: 3.5,
1408
+ * });
1409
+ * console.log(`Thumbnail URL: ${thumbnail.url}`);
1410
+ * ```
1411
+ */
1412
+ getThumbnailAndWait(request: ThumbnailRequest, options?: {
1413
+ pollInterval?: number;
1414
+ timeout?: number;
1415
+ }): Promise<ThumbnailResponse>;
1326
1416
  /**
1327
1417
  * Regenerate a thumbnail for a video (force regeneration even if one exists)
1328
1418
  *
1329
- * Useful for retrying failed thumbnail generation or regenerating with different settings.
1419
+ * Useful for retrying failed thumbnail generation or regenerating with
1420
+ * different settings. Regeneration is async: poll {@link getThumbnail} (or
1421
+ * use {@link getThumbnailAndWait}) at the same timestamp until the new
1422
+ * frame is ready.
1330
1423
  *
1331
1424
  * @example
1332
1425
  * ```typescript
1333
- * const result = await cdn.regenerateThumbnail({
1426
+ * await cdn.regenerateThumbnail({
1334
1427
  * assetId: 'video-asset-id',
1335
1428
  * timestamp: 5, // 5 seconds into the video
1336
1429
  * width: 1280,
1337
1430
  * format: 'jpg',
1338
1431
  * });
1339
- * console.log(`Thumbnail regeneration queued: ${result.status}`);
1432
+ * const thumbnail = await cdn.getThumbnailAndWait({ assetId: 'video-asset-id', timestamp: 5 });
1433
+ * console.log(`Thumbnail URL: ${thumbnail.url}`);
1340
1434
  * ```
1341
1435
  */
1342
1436
  regenerateThumbnail(request: RegenerateThumbnailRequest): Promise<RegenerateThumbnailResponse>;
@@ -1716,6 +1810,33 @@ declare class CDN {
1716
1810
  * });
1717
1811
  * console.log(`Merge job started: ${job.id}`);
1718
1812
  * ```
1813
+ *
1814
+ * @example Composite a screen recording onto a held phone ("POV holding my phone"):
1815
+ * ```typescript
1816
+ * const job = await cdn.createMergeJob({
1817
+ * projectSlug: 'my-project',
1818
+ * inputs: [
1819
+ * { assetId: 'reaction-clip-id' },
1820
+ * {
1821
+ * assetId: 'app-recording-id', // the overlay clip
1822
+ * composite: {
1823
+ * backgroundAssetId: 'holding-phone-shot-id', // image or video
1824
+ * // Corner-pin onto the angled screen (background pixel coords, TL/TR/BL/BR)
1825
+ * quad: {
1826
+ * topLeft: { x: 320, y: 480 },
1827
+ * topRight: { x: 760, y: 500 },
1828
+ * bottomLeft: { x: 300, y: 1400 },
1829
+ * bottomRight: { x: 740, y: 1440 },
1830
+ * },
1831
+ * fit: 'cover',
1832
+ * // Screen area painted #F500FA in the background is keyed out, so the
1833
+ * // recording shows through and fingers over the screen occlude it
1834
+ * chromaKey: { color: '#F500FA', similarity: 0.34, blend: 0.06 },
1835
+ * },
1836
+ * },
1837
+ * ],
1838
+ * });
1839
+ * ```
1719
1840
  */
1720
1841
  createMergeJob(request: CreateMergeJobRequest): Promise<MergeJob>;
1721
1842
  /**
@@ -1874,4 +1995,4 @@ declare class CDN {
1874
1995
  private convertImportFileDates;
1875
1996
  }
1876
1997
 
1877
- export { type Asset, type AssetStatus, type AssetType, type AudioTrackInput, type BundleDownloadUrlRequest, type BundleDownloadUrlResponse, type BundleStatus, CDN, type CancelImportResponse, type CancelMergeJobRequest, type CdnEnvironment, type CdnStorageBreakdownItem, type CdnStorageBreakdownRequest, type CdnStorageBreakdownResponse, type CdnUsageDataPoint, type CdnUsageHistoryRequest, type CdnUsageHistoryResponse, type CdnUsageRequest, type CdnUsageResponse, type ConfirmUploadRequest, type ConfirmUploadResponse, type CreateBundleRequest, type CreateBundleResponse, type CreateFolderRequest, type CreateImportRequest, type CreateImportResponse, type CreateMergeJobRequest, type DeleteAssetRequest, type DeleteAssetsRequest, type DeleteAssetsResponse, type DownloadBundle, type ExtractAudioRequest, type ExtractAudioResponse, type Folder, type FolderListItem, type FolderTreeNode, type GenerateGifRequest, type GetAssetRequest, type GetFolderByPathRequest, type GetFolderRequest, type GetFolderTreeRequest, type GetMergeJobRequest, type GifStatus, type ImageVideoWatermarkOptions, type ImageWatermarkConfig, type ImageWatermarkPosition, type ImageWatermarkSizingMode, type ImportAuthType, type ImportError, type ImportFile, type ImportFileStatus, type ImportJob, type ImportJobStatus, type ImportJobSummary, type ImportPathMode, type ListAssetsRequest, type ListAssetsResponse, type ListBundlesRequest, type ListBundlesResponse, type ListFoldersRequest, type ListFoldersResponse, type ListGifsRequest, type ListImportFilesRequest, type ListImportFilesResponse, type ListImportsRequest, type ListImportsResponse, type ListJobsRequest, type ListJobsResponse, type ListMergeJobsRequest, type ListMergeJobsResponse, type ListPrivateFilesRequest, type ListPrivateFilesResponse, type ListThumbnailsRequest, type ListThumbnailsResponse, type MergeAspectRatio, type MergeInputItem, type MergeJob, type MergeJobWithOutput, type MergeOutputConfig, type MergeOutputFormat, type MergeQuality, type MergeStatus, type MoveAssetsRequest, type MoveAssetsResponse, type MoveFolderRequest, type MoveFolderResponse, type MovePrivateFilesRequest, type MovePrivateFilesResponse, type PrivateDownloadUrlRequest, type PrivateDownloadUrlResponse, type PrivateFile, type PrivateFileStatus, type PrivateUploadUrlRequest, type PrivateUploadUrlResponse, type RegenerateThumbnailRequest, type RegenerateThumbnailResponse, type RetryImportResponse, type StreamingUrls, type TextOverlay, type TextOverlayShadow, type TextOverlayStroke, type TextVideoWatermarkOptions, type ThumbnailRequest, type ThumbnailResponse, type TranscodeJob, type TranscodeVideoRequest, type TranscodingStatus, type TransformOptions, type TrimOptions, type UpdateAssetRequest, type UpdateFolderRequest, type UpdatePrivateFileRequest, type UploadFromUrlRequest, type UploadUrlRequest, type UploadUrlResponse, type VideoCodec, type VideoGif, type VideoOutputFormat, type VideoQuality, type VideoThumbnail, type VideoVariant, type WatermarkOptions };
1998
+ export { type Asset, type AssetStatus, type AssetType, type AudioTrackInput, type BundleDownloadUrlRequest, type BundleDownloadUrlResponse, type BundleStatus, CDN, type CancelImportResponse, type CancelMergeJobRequest, type CdnEnvironment, type CdnStorageBreakdownItem, type CdnStorageBreakdownRequest, type CdnStorageBreakdownResponse, type CdnUsageDataPoint, type CdnUsageHistoryRequest, type CdnUsageHistoryResponse, type CdnUsageRequest, type CdnUsageResponse, type CompositeChromaKey, type CompositeConfig, type CompositePoint, type CompositeQuad, type CompositeRect, type ConfirmUploadRequest, type ConfirmUploadResponse, type CreateBundleRequest, type CreateBundleResponse, type CreateFolderRequest, type CreateImportRequest, type CreateImportResponse, type CreateMergeJobRequest, type DeleteAssetRequest, type DeleteAssetsRequest, type DeleteAssetsResponse, type DownloadBundle, type ExtractAudioRequest, type ExtractAudioResponse, type Folder, type FolderListItem, type FolderTreeNode, type GenerateGifRequest, type GetAssetRequest, type GetFolderByPathRequest, type GetFolderRequest, type GetFolderTreeRequest, type GetMergeJobRequest, type GifStatus, type ImageVideoWatermarkOptions, type ImageWatermarkConfig, type ImageWatermarkPosition, type ImageWatermarkSizingMode, type ImportAuthType, type ImportError, type ImportFile, type ImportFileStatus, type ImportJob, type ImportJobStatus, type ImportJobSummary, type ImportPathMode, type ListAssetsRequest, type ListAssetsResponse, type ListBundlesRequest, type ListBundlesResponse, type ListFoldersRequest, type ListFoldersResponse, type ListGifsRequest, type ListImportFilesRequest, type ListImportFilesResponse, type ListImportsRequest, type ListImportsResponse, type ListJobsRequest, type ListJobsResponse, type ListMergeJobsRequest, type ListMergeJobsResponse, type ListPrivateFilesRequest, type ListPrivateFilesResponse, type ListThumbnailsRequest, type ListThumbnailsResponse, type MergeAspectRatio, type MergeInputItem, type MergeJob, type MergeJobWithOutput, type MergeOutputConfig, type MergeOutputFormat, type MergeQuality, type MergeStatus, type MoveAssetsRequest, type MoveAssetsResponse, type MoveFolderRequest, type MoveFolderResponse, type MovePrivateFilesRequest, type MovePrivateFilesResponse, type PrivateDownloadUrlRequest, type PrivateDownloadUrlResponse, type PrivateFile, type PrivateFileStatus, type PrivateUploadUrlRequest, type PrivateUploadUrlResponse, type RegenerateThumbnailRequest, type RegenerateThumbnailResponse, type RetryImportResponse, type StreamingUrls, type TextOverlay, type TextOverlayShadow, type TextOverlayStroke, type TextVideoWatermarkOptions, type ThumbnailRequest, type ThumbnailResponse, type TranscodeJob, type TranscodeVideoRequest, type TranscodingStatus, type TransformOptions, type TrimOptions, type UpdateAssetRequest, type UpdateFolderRequest, type UpdatePrivateFileRequest, type UploadFromUrlRequest, type UploadUrlRequest, type UploadUrlResponse, type VideoCodec, type VideoGif, type VideoOutputFormat, type VideoQuality, type VideoThumbnail, type VideoVariant, type WatermarkOptions };
package/dist/cdn/index.js CHANGED
@@ -541,7 +541,12 @@ var CDN = class {
541
541
  return this.http.get(`/cdn/video/${assetId}/stream`);
542
542
  }
543
543
  /**
544
- * Generate a thumbnail from a video at a specific timestamp
544
+ * Get the thumbnail at a specific timestamp, generating it on miss
545
+ *
546
+ * If no thumbnail exists at that timestamp yet, generation is queued
547
+ * automatically and a response with `status: "pending"` (null url) is
548
+ * returned. Poll until `status` is "ready", or use
549
+ * {@link getThumbnailAndWait} to do that for you.
545
550
  *
546
551
  * @example
547
552
  * ```typescript
@@ -551,7 +556,9 @@ var CDN = class {
551
556
  * width: 320,
552
557
  * format: 'webp',
553
558
  * });
554
- * console.log(`Thumbnail URL: ${thumbnail.url}`);
559
+ * if (thumbnail.status === 'ready') {
560
+ * console.log(`Thumbnail URL: ${thumbnail.url}`);
561
+ * }
555
562
  * ```
556
563
  */
557
564
  async getThumbnail(request) {
@@ -561,20 +568,52 @@ var CDN = class {
561
568
  if (request.format) params.set("format", request.format);
562
569
  return this.http.get(`/cdn/video/thumbnail/${request.assetId}?${params.toString()}`);
563
570
  }
571
+ /**
572
+ * Get the thumbnail at a specific timestamp, waiting for generation to complete
573
+ *
574
+ * Calls {@link getThumbnail} and polls until the thumbnail is ready.
575
+ *
576
+ * @example
577
+ * ```typescript
578
+ * const thumbnail = await cdn.getThumbnailAndWait({
579
+ * assetId: 'video-asset-id',
580
+ * timestamp: 3.5,
581
+ * });
582
+ * console.log(`Thumbnail URL: ${thumbnail.url}`);
583
+ * ```
584
+ */
585
+ async getThumbnailAndWait(request, options = {}) {
586
+ const { pollInterval = 1e3, timeout = 6e4 } = options;
587
+ const startTime = Date.now();
588
+ while (true) {
589
+ const thumbnail = await this.getThumbnail(request);
590
+ if (thumbnail.status === "ready") {
591
+ return thumbnail;
592
+ }
593
+ if (Date.now() - startTime >= timeout) {
594
+ throw new Error("Thumbnail generation timed out");
595
+ }
596
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
597
+ }
598
+ }
564
599
  /**
565
600
  * Regenerate a thumbnail for a video (force regeneration even if one exists)
566
601
  *
567
- * Useful for retrying failed thumbnail generation or regenerating with different settings.
602
+ * Useful for retrying failed thumbnail generation or regenerating with
603
+ * different settings. Regeneration is async: poll {@link getThumbnail} (or
604
+ * use {@link getThumbnailAndWait}) at the same timestamp until the new
605
+ * frame is ready.
568
606
  *
569
607
  * @example
570
608
  * ```typescript
571
- * const result = await cdn.regenerateThumbnail({
609
+ * await cdn.regenerateThumbnail({
572
610
  * assetId: 'video-asset-id',
573
611
  * timestamp: 5, // 5 seconds into the video
574
612
  * width: 1280,
575
613
  * format: 'jpg',
576
614
  * });
577
- * console.log(`Thumbnail regeneration queued: ${result.status}`);
615
+ * const thumbnail = await cdn.getThumbnailAndWait({ assetId: 'video-asset-id', timestamp: 5 });
616
+ * console.log(`Thumbnail URL: ${thumbnail.url}`);
578
617
  * ```
579
618
  */
580
619
  async regenerateThumbnail(request) {
@@ -1199,6 +1238,33 @@ var CDN = class {
1199
1238
  * });
1200
1239
  * console.log(`Merge job started: ${job.id}`);
1201
1240
  * ```
1241
+ *
1242
+ * @example Composite a screen recording onto a held phone ("POV holding my phone"):
1243
+ * ```typescript
1244
+ * const job = await cdn.createMergeJob({
1245
+ * projectSlug: 'my-project',
1246
+ * inputs: [
1247
+ * { assetId: 'reaction-clip-id' },
1248
+ * {
1249
+ * assetId: 'app-recording-id', // the overlay clip
1250
+ * composite: {
1251
+ * backgroundAssetId: 'holding-phone-shot-id', // image or video
1252
+ * // Corner-pin onto the angled screen (background pixel coords, TL/TR/BL/BR)
1253
+ * quad: {
1254
+ * topLeft: { x: 320, y: 480 },
1255
+ * topRight: { x: 760, y: 500 },
1256
+ * bottomLeft: { x: 300, y: 1400 },
1257
+ * bottomRight: { x: 740, y: 1440 },
1258
+ * },
1259
+ * fit: 'cover',
1260
+ * // Screen area painted #F500FA in the background is keyed out, so the
1261
+ * // recording shows through and fingers over the screen occlude it
1262
+ * chromaKey: { color: '#F500FA', similarity: 0.34, blend: 0.06 },
1263
+ * },
1264
+ * },
1265
+ * ],
1266
+ * });
1267
+ * ```
1202
1268
  */
1203
1269
  async createMergeJob(request) {
1204
1270
  const response = await this.http.post("/cdn/video/merge", request);