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":"
|
|
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
|
|
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",
|