@bowenqt/qiniu-ai-sdk 0.19.0 → 0.20.0

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.
@@ -12,12 +12,6 @@
12
12
  * }
13
13
  * };
14
14
  * ```
15
- *
16
- * @example Node.js usage (direct signing):
17
- * ```typescript
18
- * import { createSigner } from '@bowenqt/qiniu-ai-sdk/node';
19
- * const signer = createSigner({ accessKey, secretKey });
20
- * ```
21
15
  */
22
16
  Object.defineProperty(exports, "__esModule", { value: true });
23
17
  exports.CachedSigner = exports.UrlCache = void 0;
@@ -56,7 +50,15 @@ class UrlCache {
56
50
  /** Cache a signed URL */
57
51
  set(bucket, key, signedUrl, fop) {
58
52
  const cacheKey = this.getKey(bucket, key, fop);
59
- // Evict if at capacity
53
+ // Check if key already exists (update, not new entry)
54
+ if (this.cache.has(cacheKey)) {
55
+ this.cache.set(cacheKey, {
56
+ signedUrl,
57
+ accessTime: Date.now(),
58
+ });
59
+ return;
60
+ }
61
+ // Evict if at capacity (only for new entries)
60
62
  if (this.cache.size >= this.config.maxSize) {
61
63
  this.evictLRU();
62
64
  }
@@ -113,7 +115,11 @@ class CachedSigner {
113
115
  this.cache = new UrlCache(cacheConfig);
114
116
  }
115
117
  async sign(bucket, key, options) {
116
- // Check cache first
118
+ // Skip cache if custom expiry is specified (different TTLs = different URLs)
119
+ if (options?.expiry) {
120
+ return this.baseSigner.sign(bucket, key, options);
121
+ }
122
+ // Check cache first (only for default expiry)
117
123
  const cached = this.cache.get(bucket, key, options?.fop);
118
124
  if (cached)
119
125
  return cached;
@@ -1 +1 @@
1
- {"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/lib/signer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"signer.js","sourceRoot":"","sources":["../../src/lib/signer.ts"],"names":[],"mappings":"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"}
@@ -11,12 +11,6 @@
11
11
  * }
12
12
  * };
13
13
  * ```
14
- *
15
- * @example Node.js usage (direct signing):
16
- * ```typescript
17
- * import { createSigner } from '@bowenqt/qiniu-ai-sdk/node';
18
- * const signer = createSigner({ accessKey, secretKey });
19
- * ```
20
14
  */
21
15
  /**
22
16
  * LRU cache for signed URLs.
@@ -53,7 +47,15 @@ export class UrlCache {
53
47
  /** Cache a signed URL */
54
48
  set(bucket, key, signedUrl, fop) {
55
49
  const cacheKey = this.getKey(bucket, key, fop);
56
- // Evict if at capacity
50
+ // Check if key already exists (update, not new entry)
51
+ if (this.cache.has(cacheKey)) {
52
+ this.cache.set(cacheKey, {
53
+ signedUrl,
54
+ accessTime: Date.now(),
55
+ });
56
+ return;
57
+ }
58
+ // Evict if at capacity (only for new entries)
57
59
  if (this.cache.size >= this.config.maxSize) {
58
60
  this.evictLRU();
59
61
  }
@@ -109,7 +111,11 @@ export class CachedSigner {
109
111
  this.cache = new UrlCache(cacheConfig);
110
112
  }
111
113
  async sign(bucket, key, options) {
112
- // Check cache first
114
+ // Skip cache if custom expiry is specified (different TTLs = different URLs)
115
+ if (options?.expiry) {
116
+ return this.baseSigner.sign(bucket, key, options);
117
+ }
118
+ // Check cache first (only for default expiry)
113
119
  const cached = this.cache.get(bucket, key, options?.fop);
114
120
  if (cached)
115
121
  return cached;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Video Frame Extraction using Qiniu vframe API.
3
+ *
4
+ * @see https://developer.qiniu.com/dora/1313/video-frame-thumbnails-vframe
5
+ */
6
+ import type { QiniuSigner } from './signer';
7
+ import type { QiniuAsset, ResolveOptions } from './asset-resolver';
8
+ /** Vframe extraction options */
9
+ export interface VframeOptions {
10
+ /** Specific second offsets to extract frames */
11
+ offsets?: number[];
12
+ /** Number of frames to extract (uniformly distributed) */
13
+ count?: number;
14
+ /** Video duration in seconds (required for count mode) */
15
+ duration?: number;
16
+ /** Frame width (default: 640, range: 20-3840) */
17
+ width?: number;
18
+ /** Frame height (default: auto, range: 20-2160) */
19
+ height?: number;
20
+ /** Output format (default: 'jpg') */
21
+ format?: 'jpg' | 'png';
22
+ /** Rotation in degrees */
23
+ rotate?: 90 | 180 | 270 | 'auto';
24
+ }
25
+ /** Extracted video frame */
26
+ export interface VideoFrame {
27
+ /** Signed frame URL */
28
+ url: string;
29
+ /** URL expiration timestamp (ms) */
30
+ expiresAt: number;
31
+ /** Frame offset in seconds */
32
+ offset: number;
33
+ /** Frame index (0-based) */
34
+ index: number;
35
+ }
36
+ /** Frame extraction result */
37
+ export interface VframeResult {
38
+ /** Extracted frames */
39
+ frames: VideoFrame[];
40
+ /** Total frame count */
41
+ count: number;
42
+ /** Video duration (if provided) */
43
+ duration?: number;
44
+ }
45
+ export declare class VframeError extends Error {
46
+ constructor(message: string);
47
+ }
48
+ /**
49
+ * Build a vframe fop command.
50
+ *
51
+ * @example
52
+ * ```typescript
53
+ * buildVframeFop(5, { width: 320, format: 'jpg' });
54
+ * // 'vframe/jpg/offset/5/w/320'
55
+ * ```
56
+ */
57
+ export declare function buildVframeFop(offset: number, options?: Omit<VframeOptions, 'offsets' | 'count' | 'duration'>): string;
58
+ /**
59
+ * Build a complete vframe URL from a base URL.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * buildVframeUrl('https://cdn.example.com/video.mp4', 5, { width: 320 });
64
+ * // 'https://cdn.example.com/video.mp4?vframe/jpg/offset/5/w/320'
65
+ * ```
66
+ */
67
+ export declare function buildVframeUrl(baseUrl: string, offset: number, options?: Omit<VframeOptions, 'offsets' | 'count' | 'duration'>): string;
68
+ /**
69
+ * Extract frames from a video.
70
+ *
71
+ * @example Uniform extraction
72
+ * ```typescript
73
+ * const result = await extractFrames(asset, signer, {
74
+ * count: 5,
75
+ * duration: 120, // 2 minute video
76
+ * width: 640,
77
+ * });
78
+ * // Frames at: 20s, 40s, 60s, 80s, 100s
79
+ * ```
80
+ *
81
+ * @example Explicit offsets
82
+ * ```typescript
83
+ * const result = await extractFrames(asset, signer, {
84
+ * offsets: [0, 30, 60, 90],
85
+ * width: 640,
86
+ * });
87
+ * ```
88
+ */
89
+ export declare function extractFrames(asset: QiniuAsset, signer: QiniuSigner, options: VframeOptions, resolveOptions?: ResolveOptions): Promise<VframeResult>;
90
+ /**
91
+ * Extract a single frame at a specific offset.
92
+ */
93
+ export declare function extractFrame(asset: QiniuAsset, signer: QiniuSigner, offset: number, options?: Omit<VframeOptions, 'offsets' | 'count' | 'duration'>, resolveOptions?: ResolveOptions): Promise<VideoFrame>;
94
+ //# sourceMappingURL=vframe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vframe.d.ts","sourceRoot":"","sources":["../../src/lib/vframe.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,UAAU,CAAC;AACvD,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAOnE,gCAAgC;AAChC,MAAM,WAAW,aAAa;IAE1B,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAGnB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAGlB,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACvB,0BAA0B;IAC1B,MAAM,CAAC,EAAE,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,MAAM,CAAC;CACpC;AAED,4BAA4B;AAC5B,MAAM,WAAW,UAAU;IACvB,uBAAuB;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,8BAA8B;AAC9B,MAAM,WAAW,YAAY;IACzB,uBAAuB;IACvB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAiBD,qBAAa,WAAY,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI9B;AAMD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,CAAM,GAAG,MAAM,CA8B1H;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC1B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,CAAM,GACpE,MAAM,CAIR;AA6BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,aAAa,CAC/B,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,aAAa,EACtB,cAAc,CAAC,EAAE,cAAc,GAChC,OAAO,CAAC,YAAY,CAAC,CAuDvB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAC9B,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,IAAI,CAAC,aAAa,EAAE,SAAS,GAAG,OAAO,GAAG,UAAU,CAAM,EACnE,cAAc,CAAC,EAAE,cAAc,GAChC,OAAO,CAAC,UAAU,CAAC,CAMrB"}
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ /**
3
+ * Video Frame Extraction using Qiniu vframe API.
4
+ *
5
+ * @see https://developer.qiniu.com/dora/1313/video-frame-thumbnails-vframe
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.VframeError = void 0;
9
+ exports.buildVframeFop = buildVframeFop;
10
+ exports.buildVframeUrl = buildVframeUrl;
11
+ exports.extractFrames = extractFrames;
12
+ exports.extractFrame = extractFrame;
13
+ const asset_resolver_1 = require("./asset-resolver");
14
+ // ============================================================================
15
+ // Constants
16
+ // ============================================================================
17
+ const DEFAULT_COUNT = 5;
18
+ const DEFAULT_WIDTH = 640;
19
+ const DEFAULT_FORMAT = 'jpg';
20
+ const MIN_DIMENSION = 20;
21
+ const MAX_LONG_EDGE = 3840;
22
+ const MAX_SHORT_EDGE = 2160;
23
+ // ============================================================================
24
+ // Errors
25
+ // ============================================================================
26
+ class VframeError extends Error {
27
+ constructor(message) {
28
+ super(message);
29
+ this.name = 'VframeError';
30
+ }
31
+ }
32
+ exports.VframeError = VframeError;
33
+ // ============================================================================
34
+ // URL Building
35
+ // ============================================================================
36
+ /**
37
+ * Build a vframe fop command.
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * buildVframeFop(5, { width: 320, format: 'jpg' });
42
+ * // 'vframe/jpg/offset/5/w/320'
43
+ * ```
44
+ */
45
+ function buildVframeFop(offset, options = {}) {
46
+ if (offset < 0) {
47
+ throw new VframeError('Offset must be non-negative');
48
+ }
49
+ const format = options.format ?? DEFAULT_FORMAT;
50
+ const parts = [`vframe/${format}/offset/${offset}`];
51
+ // Width
52
+ if (options.width !== undefined) {
53
+ if (options.width < MIN_DIMENSION || options.width > MAX_LONG_EDGE) {
54
+ throw new VframeError(`Width must be between ${MIN_DIMENSION} and ${MAX_LONG_EDGE}`);
55
+ }
56
+ parts.push(`w/${options.width}`);
57
+ }
58
+ // Height
59
+ if (options.height !== undefined) {
60
+ if (options.height < MIN_DIMENSION || options.height > MAX_SHORT_EDGE) {
61
+ throw new VframeError(`Height must be between ${MIN_DIMENSION} and ${MAX_SHORT_EDGE}`);
62
+ }
63
+ parts.push(`h/${options.height}`);
64
+ }
65
+ // Rotate
66
+ if (options.rotate !== undefined) {
67
+ parts.push(`rotate/${options.rotate}`);
68
+ }
69
+ return parts.join('/');
70
+ }
71
+ /**
72
+ * Build a complete vframe URL from a base URL.
73
+ *
74
+ * @example
75
+ * ```typescript
76
+ * buildVframeUrl('https://cdn.example.com/video.mp4', 5, { width: 320 });
77
+ * // 'https://cdn.example.com/video.mp4?vframe/jpg/offset/5/w/320'
78
+ * ```
79
+ */
80
+ function buildVframeUrl(baseUrl, offset, options = {}) {
81
+ const fop = buildVframeFop(offset, options);
82
+ const separator = baseUrl.includes('?') ? '|' : '?';
83
+ return `${baseUrl}${separator}${fop}`;
84
+ }
85
+ // ============================================================================
86
+ // Frame Extraction
87
+ // ============================================================================
88
+ /**
89
+ * Calculate uniform offsets for frame extraction.
90
+ */
91
+ function calculateUniformOffsets(count, duration) {
92
+ if (count <= 0) {
93
+ throw new VframeError('Count must be positive');
94
+ }
95
+ if (duration <= 0) {
96
+ throw new VframeError('Duration must be positive');
97
+ }
98
+ if (count === 1) {
99
+ return [duration / 2];
100
+ }
101
+ const offsets = [];
102
+ const step = duration / (count + 1);
103
+ for (let i = 1; i <= count; i++) {
104
+ offsets.push(Math.round(step * i * 100) / 100);
105
+ }
106
+ return offsets;
107
+ }
108
+ /**
109
+ * Extract frames from a video.
110
+ *
111
+ * @example Uniform extraction
112
+ * ```typescript
113
+ * const result = await extractFrames(asset, signer, {
114
+ * count: 5,
115
+ * duration: 120, // 2 minute video
116
+ * width: 640,
117
+ * });
118
+ * // Frames at: 20s, 40s, 60s, 80s, 100s
119
+ * ```
120
+ *
121
+ * @example Explicit offsets
122
+ * ```typescript
123
+ * const result = await extractFrames(asset, signer, {
124
+ * offsets: [0, 30, 60, 90],
125
+ * width: 640,
126
+ * });
127
+ * ```
128
+ */
129
+ async function extractFrames(asset, signer, options, resolveOptions) {
130
+ // Validate options
131
+ if (options.offsets && options.count) {
132
+ throw new VframeError('Cannot specify both offsets and count');
133
+ }
134
+ let offsets;
135
+ if (options.offsets) {
136
+ // Explicit offsets mode
137
+ offsets = options.offsets;
138
+ for (const offset of offsets) {
139
+ if (offset < 0) {
140
+ throw new VframeError('All offsets must be non-negative');
141
+ }
142
+ }
143
+ }
144
+ else {
145
+ // Uniform extraction mode
146
+ const count = options.count ?? DEFAULT_COUNT;
147
+ if (!options.duration) {
148
+ throw new VframeError('Duration is required for uniform extraction');
149
+ }
150
+ offsets = calculateUniformOffsets(count, options.duration);
151
+ }
152
+ // Build frame extraction options
153
+ const frameOpts = {
154
+ width: options.width ?? DEFAULT_WIDTH,
155
+ height: options.height,
156
+ format: options.format ?? DEFAULT_FORMAT,
157
+ rotate: options.rotate,
158
+ };
159
+ // Extract frames in parallel
160
+ const frames = await Promise.all(offsets.map(async (offset, index) => {
161
+ const fop = buildVframeFop(offset, frameOpts);
162
+ const resolved = await (0, asset_resolver_1.resolveAsset)(asset, signer, {
163
+ ...resolveOptions,
164
+ fop,
165
+ });
166
+ return {
167
+ url: resolved.url,
168
+ expiresAt: resolved.expiresAt,
169
+ offset,
170
+ index,
171
+ };
172
+ }));
173
+ return {
174
+ frames,
175
+ count: frames.length,
176
+ duration: options.duration,
177
+ };
178
+ }
179
+ /**
180
+ * Extract a single frame at a specific offset.
181
+ */
182
+ async function extractFrame(asset, signer, offset, options = {}, resolveOptions) {
183
+ const result = await extractFrames(asset, signer, {
184
+ offsets: [offset],
185
+ ...options,
186
+ }, resolveOptions);
187
+ return result.frames[0];
188
+ }
189
+ //# sourceMappingURL=vframe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vframe.js","sourceRoot":"","sources":["../../src/lib/vframe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Video Frame Extraction using Qiniu vframe API.
3
+ *
4
+ * @see https://developer.qiniu.com/dora/1313/video-frame-thumbnails-vframe
5
+ */
6
+ import { resolveAsset } from './asset-resolver.mjs';
7
+ // ============================================================================
8
+ // Constants
9
+ // ============================================================================
10
+ const DEFAULT_COUNT = 5;
11
+ const DEFAULT_WIDTH = 640;
12
+ const DEFAULT_FORMAT = 'jpg';
13
+ const MIN_DIMENSION = 20;
14
+ const MAX_LONG_EDGE = 3840;
15
+ const MAX_SHORT_EDGE = 2160;
16
+ // ============================================================================
17
+ // Errors
18
+ // ============================================================================
19
+ export class VframeError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = 'VframeError';
23
+ }
24
+ }
25
+ // ============================================================================
26
+ // URL Building
27
+ // ============================================================================
28
+ /**
29
+ * Build a vframe fop command.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * buildVframeFop(5, { width: 320, format: 'jpg' });
34
+ * // 'vframe/jpg/offset/5/w/320'
35
+ * ```
36
+ */
37
+ export function buildVframeFop(offset, options = {}) {
38
+ if (offset < 0) {
39
+ throw new VframeError('Offset must be non-negative');
40
+ }
41
+ const format = options.format ?? DEFAULT_FORMAT;
42
+ const parts = [`vframe/${format}/offset/${offset}`];
43
+ // Width
44
+ if (options.width !== undefined) {
45
+ if (options.width < MIN_DIMENSION || options.width > MAX_LONG_EDGE) {
46
+ throw new VframeError(`Width must be between ${MIN_DIMENSION} and ${MAX_LONG_EDGE}`);
47
+ }
48
+ parts.push(`w/${options.width}`);
49
+ }
50
+ // Height
51
+ if (options.height !== undefined) {
52
+ if (options.height < MIN_DIMENSION || options.height > MAX_SHORT_EDGE) {
53
+ throw new VframeError(`Height must be between ${MIN_DIMENSION} and ${MAX_SHORT_EDGE}`);
54
+ }
55
+ parts.push(`h/${options.height}`);
56
+ }
57
+ // Rotate
58
+ if (options.rotate !== undefined) {
59
+ parts.push(`rotate/${options.rotate}`);
60
+ }
61
+ return parts.join('/');
62
+ }
63
+ /**
64
+ * Build a complete vframe URL from a base URL.
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * buildVframeUrl('https://cdn.example.com/video.mp4', 5, { width: 320 });
69
+ * // 'https://cdn.example.com/video.mp4?vframe/jpg/offset/5/w/320'
70
+ * ```
71
+ */
72
+ export function buildVframeUrl(baseUrl, offset, options = {}) {
73
+ const fop = buildVframeFop(offset, options);
74
+ const separator = baseUrl.includes('?') ? '|' : '?';
75
+ return `${baseUrl}${separator}${fop}`;
76
+ }
77
+ // ============================================================================
78
+ // Frame Extraction
79
+ // ============================================================================
80
+ /**
81
+ * Calculate uniform offsets for frame extraction.
82
+ */
83
+ function calculateUniformOffsets(count, duration) {
84
+ if (count <= 0) {
85
+ throw new VframeError('Count must be positive');
86
+ }
87
+ if (duration <= 0) {
88
+ throw new VframeError('Duration must be positive');
89
+ }
90
+ if (count === 1) {
91
+ return [duration / 2];
92
+ }
93
+ const offsets = [];
94
+ const step = duration / (count + 1);
95
+ for (let i = 1; i <= count; i++) {
96
+ offsets.push(Math.round(step * i * 100) / 100);
97
+ }
98
+ return offsets;
99
+ }
100
+ /**
101
+ * Extract frames from a video.
102
+ *
103
+ * @example Uniform extraction
104
+ * ```typescript
105
+ * const result = await extractFrames(asset, signer, {
106
+ * count: 5,
107
+ * duration: 120, // 2 minute video
108
+ * width: 640,
109
+ * });
110
+ * // Frames at: 20s, 40s, 60s, 80s, 100s
111
+ * ```
112
+ *
113
+ * @example Explicit offsets
114
+ * ```typescript
115
+ * const result = await extractFrames(asset, signer, {
116
+ * offsets: [0, 30, 60, 90],
117
+ * width: 640,
118
+ * });
119
+ * ```
120
+ */
121
+ export async function extractFrames(asset, signer, options, resolveOptions) {
122
+ // Validate options
123
+ if (options.offsets && options.count) {
124
+ throw new VframeError('Cannot specify both offsets and count');
125
+ }
126
+ let offsets;
127
+ if (options.offsets) {
128
+ // Explicit offsets mode
129
+ offsets = options.offsets;
130
+ for (const offset of offsets) {
131
+ if (offset < 0) {
132
+ throw new VframeError('All offsets must be non-negative');
133
+ }
134
+ }
135
+ }
136
+ else {
137
+ // Uniform extraction mode
138
+ const count = options.count ?? DEFAULT_COUNT;
139
+ if (!options.duration) {
140
+ throw new VframeError('Duration is required for uniform extraction');
141
+ }
142
+ offsets = calculateUniformOffsets(count, options.duration);
143
+ }
144
+ // Build frame extraction options
145
+ const frameOpts = {
146
+ width: options.width ?? DEFAULT_WIDTH,
147
+ height: options.height,
148
+ format: options.format ?? DEFAULT_FORMAT,
149
+ rotate: options.rotate,
150
+ };
151
+ // Extract frames in parallel
152
+ const frames = await Promise.all(offsets.map(async (offset, index) => {
153
+ const fop = buildVframeFop(offset, frameOpts);
154
+ const resolved = await resolveAsset(asset, signer, {
155
+ ...resolveOptions,
156
+ fop,
157
+ });
158
+ return {
159
+ url: resolved.url,
160
+ expiresAt: resolved.expiresAt,
161
+ offset,
162
+ index,
163
+ };
164
+ }));
165
+ return {
166
+ frames,
167
+ count: frames.length,
168
+ duration: options.duration,
169
+ };
170
+ }
171
+ /**
172
+ * Extract a single frame at a specific offset.
173
+ */
174
+ export async function extractFrame(asset, signer, offset, options = {}, resolveOptions) {
175
+ const result = await extractFrames(asset, signer, {
176
+ offsets: [offset],
177
+ ...options,
178
+ }, resolveOptions);
179
+ return result.frames[0];
180
+ }
181
+ //# sourceMappingURL=vframe.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bowenqt/qiniu-ai-sdk",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "TypeScript SDK for Qiniu Cloud AI Token API - Chat, Image, Video generation and more",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",