vidspotai-shared 1.0.51 → 1.0.52

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.
@@ -1 +1 @@
1
- {"version":3,"file":"google.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/google.service.ts"],"names":[],"mappings":"AAaA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AA6BlB,qBAAa,aAAc,SAAQ,wBAAwB;IACzD,OAAO,CAAC,EAAE,CAAc;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAK;;IAO/C;;;;OAIG;YACW,kBAAkB;IA0B1B,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAyC3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAuEjD,aAAa,CAAC,EAAE,QAAQ,EAAE,QAAY,EAAE,UAAmB,EAAE,SAAiB,EAAE,EAAE,iBAAiB,GAAG,MAAM;CAiB7G"}
1
+ {"version":3,"file":"google.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/google.service.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AA0DlB,qBAAa,aAAc,SAAQ,wBAAwB;IACzD,OAAO,CAAC,EAAE,CAAc;IACxB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAK;;IAO/C;;;;OAIG;YACW,kBAAkB;IA0B1B,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAqE3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAuFjD,aAAa,CAAC,EAAE,QAAQ,EAAE,QAAY,EAAE,UAAmB,EAAE,SAAiB,EAAE,EAAE,iBAAiB,GAAG,MAAM;CAiB7G"}
@@ -12,6 +12,7 @@ const types_1 = require("../../../../globals/types");
12
12
  const firebase_1 = require("../../../../libs/firebase");
13
13
  const helpers_1 = require("../../../../utils/helpers");
14
14
  const logger_1 = require("../../../../utils/logger");
15
+ const errors_1 = require("../../../../utils/errors");
15
16
  const helpers_2 = require("../../helpers");
16
17
  const baseAiGenProvider_service_1 = require("../baseAiGenProvider.service");
17
18
  const fs_1 = require("fs");
@@ -31,6 +32,35 @@ const TRANSIENT_NETWORK_CODES = new Set([
31
32
  "UND_ERR_HEADERS_TIMEOUT",
32
33
  "UND_ERR_BODY_TIMEOUT",
33
34
  ]);
35
+ // Pick a Veo-compatible image MIME type. Veo accepts image/jpeg, image/png,
36
+ // image/webp. We prefer the upstream Content-Type header (Firebase Storage
37
+ // returns the type set at upload), then fall back to URL extension, then to
38
+ // image/jpeg as a reasonable last resort for phone-camera uploads.
39
+ const VEO_SUPPORTED_IMAGE_MIMES = new Set([
40
+ "image/jpeg",
41
+ "image/png",
42
+ "image/webp",
43
+ ]);
44
+ function pickImageMimeType(contentType, url) {
45
+ const normalized = contentType.split(";")[0]?.trim().toLowerCase();
46
+ if (normalized && VEO_SUPPORTED_IMAGE_MIMES.has(normalized)) {
47
+ return normalized;
48
+ }
49
+ // Map common but non-canonical types
50
+ if (normalized === "image/jpg")
51
+ return "image/jpeg";
52
+ // Extension fallback. Strip query string first (Firebase URLs have ?alt=media).
53
+ const pathOnly = url.split("?")[0]?.toLowerCase() ?? "";
54
+ if (pathOnly.endsWith(".png"))
55
+ return "image/png";
56
+ if (pathOnly.endsWith(".webp"))
57
+ return "image/webp";
58
+ if (pathOnly.endsWith(".jpg") || pathOnly.endsWith(".jpeg"))
59
+ return "image/jpeg";
60
+ // Phone uploads default to JPEG far more often than PNG, so it's a safer
61
+ // last-resort than the previous hardcoded "image/png".
62
+ return "image/jpeg";
63
+ }
34
64
  function isTransientFetchError(err) {
35
65
  if (!err)
36
66
  return false;
@@ -93,9 +123,28 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
93
123
  };
94
124
  if (params.inputImageUrl) {
95
125
  const imgResp = await this.withTransientRetry("input-image fetch", () => fetch(params.inputImageUrl));
126
+ // BUG #3 fix: fetch() does NOT throw on 4xx/5xx — it returns a Response
127
+ // with the error body. Without this check we'd encode an HTML error page
128
+ // as if it were image bytes, and Veo would silently reject it.
129
+ if (!imgResp.ok) {
130
+ throw new errors_1.UserFacingError(`Input image could not be downloaded (HTTP ${imgResp.status}). The image URL may have expired or been deleted.`);
131
+ }
96
132
  const imgBuffer = Buffer.from(await imgResp.arrayBuffer());
133
+ // BUG #2 fix: a 0-byte body slips through fetch().ok if the upstream
134
+ // wrote an empty file. Veo treats empty bytes as content-filtered (no
135
+ // error, no video). Surface the real cause to the user.
136
+ if (imgBuffer.length === 0) {
137
+ throw new errors_1.UserFacingError("Input image is empty (0 bytes). Please re-upload the image.");
138
+ }
139
+ // BUG #1 fix: previously hardcoded "image/png" regardless of the actual
140
+ // upload type. Veo rejects JPEG bytes labeled as PNG, with no error
141
+ // detail. Prefer the Content-Type from the response (authoritative —
142
+ // Firebase Storage sets it from upload metadata), fall back to URL
143
+ // extension, then default to JPEG (modern phone-camera default).
144
+ const respContentType = imgResp.headers.get("content-type") ?? "";
145
+ const mimeType = pickImageMimeType(respContentType, params.inputImageUrl);
97
146
  request.image = {
98
- mimeType: "image/png", // or infer from URL (jpg/png)
147
+ mimeType,
99
148
  imageBytes: imgBuffer.toString("base64"),
100
149
  };
101
150
  }
@@ -118,6 +167,22 @@ class GoogleService extends baseAiGenProvider_service_1.BaseAiGenProviderService
118
167
  }
119
168
  const videoUri = result.response?.generatedVideos?.[0]?.video?.uri;
120
169
  if (!videoUri) {
170
+ // Veo silently filters content via RAI without setting result.error.
171
+ // When raiMediaFilteredCount > 0, the request was blocked by safety
172
+ // filters. Surface the reasons so we can show the user what to fix,
173
+ // and prefix with `user_input_error:` so sceneMonitor demotes the log
174
+ // level (these are expected user-input failures, not bugs).
175
+ const filteredCount = result.response?.raiMediaFilteredCount ?? 0;
176
+ const filteredReasons = result.response?.raiMediaFilteredReasons ?? [];
177
+ if (filteredCount > 0 || filteredReasons.length > 0) {
178
+ const reasons = filteredReasons.length > 0
179
+ ? filteredReasons.join("; ")
180
+ : "Content blocked by Google safety filters";
181
+ return {
182
+ status: types_1.EVideoSceneStatus.FAILED,
183
+ errorMessage: `user_input_error: ${reasons}`,
184
+ };
185
+ }
121
186
  return {
122
187
  status: types_1.EVideoSceneStatus.FAILED,
123
188
  errorMessage: "No video URL or VideoBytes found in response",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vidspotai-shared",
3
- "version": "1.0.51",
3
+ "version": "1.0.52",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "exports": {