fix
This commit is contained in:
@@ -25,7 +25,9 @@ import java.util.*;
|
||||
public class GeminiVideoGenerationService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GeminiVideoGenerationService.class);
|
||||
private static final String VERTEX_API_TEMPLATE = "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:predict";
|
||||
|
||||
// ИЗМЕНЕНИЕ 1: Правильный эндпоинт для видео (PredictLongRunning)
|
||||
private static final String VERTEX_API_TEMPLATE = "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:predictLongRunning";
|
||||
private static final String CREDENTIALS_FILE_PATH = "keys/google-key.json";
|
||||
|
||||
private final WebClient webClient;
|
||||
@@ -42,10 +44,10 @@ public class GeminiVideoGenerationService {
|
||||
public GeminiVideoGenerationService() {
|
||||
HttpClient httpClient = HttpClient.create()
|
||||
.resolver(DefaultAddressResolverGroup.INSTANCE)
|
||||
.responseTimeout(Duration.ofMillis(600000)); // 10 минут таймаут
|
||||
.responseTimeout(Duration.ofMillis(600000)); // 10 минут
|
||||
|
||||
ExchangeStrategies strategies = ExchangeStrategies.builder()
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(150 * 1024 * 1024)) // 150 MB под видео
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(150 * 1024 * 1024))
|
||||
.build();
|
||||
|
||||
this.webClient = WebClient.builder()
|
||||
@@ -54,52 +56,8 @@ public class GeminiVideoGenerationService {
|
||||
.build();
|
||||
}
|
||||
|
||||
// НОВЫЙ МЕТОД: Полная диагностика перед запуском
|
||||
private boolean runPreflightCheck() {
|
||||
logger.info("=== СТАРТ ДИАГНОСТИКИ VEO ===");
|
||||
|
||||
// Шаг 1. Проверяем переменные окружения
|
||||
if (projectId == null || projectId.trim().isEmpty()) {
|
||||
logger.error("[ПРОВАЛ] Project ID не задан в application.properties!");
|
||||
return false;
|
||||
}
|
||||
logger.info("[ОК] Project ID: {}", projectId);
|
||||
logger.info("[ОК] Location: {}", location);
|
||||
logger.info("[ОК] Model: {}", model);
|
||||
|
||||
// Шаг 2. Проверяем файл ключа
|
||||
ClassPathResource resource = new ClassPathResource(CREDENTIALS_FILE_PATH);
|
||||
if (!resource.exists()) {
|
||||
logger.error("[ПРОВАЛ] Файл ключа не найден по пути: src/main/resources/{}", CREDENTIALS_FILE_PATH);
|
||||
return false;
|
||||
}
|
||||
logger.info("[ОК] Файл ключа найден.");
|
||||
|
||||
// Шаг 3. Проверяем генерацию токена
|
||||
String token = getAccessTokenFromResources();
|
||||
if (token == null || token.isEmpty()) {
|
||||
logger.error("[ПРОВАЛ] Не удалось сгенерировать Access Token. Проверьте содержимое ключа и права аккаунта!");
|
||||
return false;
|
||||
}
|
||||
logger.info("[ОК] Access Token успешно сгенерирован (длина: {})", token.length());
|
||||
|
||||
// Шаг 4. Проверяем URL
|
||||
String endpointUrl = String.format(VERTEX_API_TEMPLATE, location, projectId, location, model);
|
||||
logger.info("[ОК] Endpoint URL сформирован: {}", endpointUrl);
|
||||
|
||||
logger.info("=== ДИАГНОСТИКА УСПЕШНО ЗАВЕРШЕНА ===");
|
||||
return true;
|
||||
}
|
||||
|
||||
public byte[] generateVideo(String prompt) {
|
||||
// Запускаем проверку перед каждой генерацией
|
||||
if (!runPreflightCheck()) {
|
||||
logger.error("VEO: Генерация отменена из-за провала базовых проверок.");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
String accessToken = getAccessTokenFromResources();
|
||||
String endpointUrl = String.format(VERTEX_API_TEMPLATE, location, projectId, location, model);
|
||||
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
@@ -113,37 +71,63 @@ public class GeminiVideoGenerationService {
|
||||
parameters.put("aspectRatio", "9:16");
|
||||
requestBody.put("parameters", parameters);
|
||||
|
||||
// Логируем сам запрос, чтобы проверить его формат
|
||||
logger.info("VEO: Отправка JSON запроса в Google: {}", requestBody);
|
||||
logger.info("VEO: Запускаем генерацию (Long Running Operation). Промпт: {}", prompt);
|
||||
|
||||
Map<String, Object> response = webClient.post()
|
||||
// ШАГ 1: Запускаем задачу
|
||||
Map<String, Object> initResponse = webClient.post()
|
||||
.uri(URI.create(endpointUrl))
|
||||
.header("Authorization", "Bearer " + accessToken)
|
||||
.header("Authorization", "Bearer " + getAccessTokenFromResources())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.bodyValue(requestBody)
|
||||
.retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
|
||||
// ВНИМАНИЕ: МЫ ОТКЛЮЧИЛИ РЕТРАИ ДЛЯ ОТЛОВА ЧИСТОЙ ОШИБКИ ОТ GOOGLE
|
||||
// .retryWhen(reactor.util.retry.Retry.backoff(3, Duration.ofSeconds(15))
|
||||
// .filter(t -> t instanceof WebClientResponseException &&
|
||||
// ((WebClientResponseException) t).getStatusCode().value() == 429))
|
||||
.block(Duration.ofMillis(600000));
|
||||
.block(Duration.ofSeconds(30));
|
||||
|
||||
byte[] videoBytes = extractVideoFromResponse(response);
|
||||
if (videoBytes != null) {
|
||||
logger.info("VEO: Успех! Видео сгенерировано. Размер: {} байт", videoBytes.length);
|
||||
} else {
|
||||
logger.error("VEO: Видео не найдено в ответе. Ответ от Google: {}", response);
|
||||
if (initResponse == null || !initResponse.containsKey("name")) {
|
||||
logger.error("VEO: Ошибка! Не получен ID операции. Ответ: {}", initResponse);
|
||||
return null;
|
||||
}
|
||||
return videoBytes;
|
||||
|
||||
String operationName = (String) initResponse.get("name");
|
||||
logger.info("VEO: Задача успешно принята Google. ID операции: {}", operationName);
|
||||
|
||||
// ШАГ 2: Цикл опроса (Polling)
|
||||
String operationUrl = String.format("https://%s-aiplatform.googleapis.com/v1/%s", location, operationName);
|
||||
int attempts = 0;
|
||||
int maxAttempts = 60; // Максимум 15 минут (60 попыток по 15 сек)
|
||||
|
||||
while (attempts < maxAttempts) {
|
||||
Thread.sleep(15000); // Ждем 15 секунд перед проверкой
|
||||
attempts++;
|
||||
logger.info("VEO: Проверка статуса видео... (попытка {}/{})", attempts, maxAttempts);
|
||||
|
||||
Map<String, Object> statusResponse = webClient.get()
|
||||
.uri(URI.create(operationUrl))
|
||||
.header("Authorization", "Bearer " + getAccessTokenFromResources()) // Обновляем токен
|
||||
.retrieve()
|
||||
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {})
|
||||
.block(Duration.ofSeconds(30));
|
||||
|
||||
if (statusResponse != null) {
|
||||
if (statusResponse.containsKey("error")) {
|
||||
logger.error("VEO: Ошибка внутри рендеринга видео: {}", statusResponse.get("error"));
|
||||
return null;
|
||||
}
|
||||
|
||||
Boolean isDone = (Boolean) statusResponse.get("done");
|
||||
if (Boolean.TRUE.equals(isDone)) {
|
||||
logger.info("VEO: 🔥 Видео готово! Скачиваем результат...");
|
||||
Map<String, Object> responseObj = (Map<String, Object>) statusResponse.get("response");
|
||||
return extractVideoFromResponse(responseObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.error("VEO: Превышено время ожидания рендеринга (таймаут)!");
|
||||
return null;
|
||||
|
||||
} catch (WebClientResponseException e) {
|
||||
// САМОЕ ВАЖНОЕ: Расшифровка ответа от Google
|
||||
logger.error("==================================================");
|
||||
logger.error("VEO API ERROR CRASH!");
|
||||
logger.error("HTTP Status Code: {}", e.getStatusCode());
|
||||
logger.error("Error Body from Google: {}", e.getResponseBodyAsString());
|
||||
logger.error("==================================================");
|
||||
logger.error("VEO API ERROR! Status: {}, Body: {}", e.getStatusCode(), e.getResponseBodyAsString());
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.error("VEO API Неизвестная ошибка: {}", e.getMessage(), e);
|
||||
@@ -162,7 +146,7 @@ public class GeminiVideoGenerationService {
|
||||
return credentials.getAccessToken().getTokenValue();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
logger.error("Ошибка при чтении файла ключа: {}", e.getMessage());
|
||||
logger.error("Ошибка ключа: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -174,21 +158,16 @@ public class GeminiVideoGenerationService {
|
||||
List<Map<String, Object>> predictions = (List<Map<String, Object>>) response.get("predictions");
|
||||
if (predictions != null && !predictions.isEmpty()) {
|
||||
Map<String, Object> firstPrediction = predictions.get(0);
|
||||
|
||||
// Проверяем разные варианты ключей, в которых Google может вернуть видео
|
||||
String base64Video = (String) firstPrediction.get("bytesBase64Encoded");
|
||||
if (base64Video == null) {
|
||||
base64Video = (String) firstPrediction.get("b64");
|
||||
}
|
||||
|
||||
if (base64Video != null) {
|
||||
return java.util.Base64.getDecoder().decode(base64Video);
|
||||
} else {
|
||||
logger.warn("VEO: Ключ с Base64 видео не найден в 'predictions'. Доступные ключи: {}", firstPrediction.keySet());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("VEO: Ошибка парсинга ответа: {}", e.getMessage());
|
||||
logger.error("VEO: Ошибка извлечения Base64: {}", e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user