From e67fa89905af5878417b89a78a519734082794ab Mon Sep 17 00:00:00 2001 From: arys Date: Tue, 3 Mar 2026 19:31:30 +0500 Subject: [PATCH] fix --- .../service/GeminiVideoGenerationService.java | 83 ++++++++++--------- .../service/MarketingStrategyV3Service.java | 4 +- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/main/java/kz/konturai/parser/service/GeminiVideoGenerationService.java b/src/main/java/kz/konturai/parser/service/GeminiVideoGenerationService.java index 83fec1c..a592a72 100644 --- a/src/main/java/kz/konturai/parser/service/GeminiVideoGenerationService.java +++ b/src/main/java/kz/konturai/parser/service/GeminiVideoGenerationService.java @@ -25,9 +25,6 @@ import java.util.*; public class GeminiVideoGenerationService { private static final Logger logger = LoggerFactory.getLogger(GeminiVideoGenerationService.class); - - // ИСПОЛЬЗУЕМ СТРОГО v1beta1 (иначе Google не понимает операции для новых моделей) - private static final String VERTEX_API_BASE = "https://%s-aiplatform.googleapis.com/v1beta1"; private static final String CREDENTIALS_FILE_PATH = "keys/google-key.json"; private final WebClient webClient; @@ -44,10 +41,10 @@ public class GeminiVideoGenerationService { public GeminiVideoGenerationService() { HttpClient httpClient = HttpClient.create() .resolver(DefaultAddressResolverGroup.INSTANCE) - .responseTimeout(Duration.ofMillis(600000)); + .responseTimeout(Duration.ofMinutes(15)); // Долгий таймаут для видео ExchangeStrategies strategies = ExchangeStrategies.builder() - .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(150 * 1024 * 1024)) + .codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(200 * 1024 * 1024)) // 200MB лимит для больших видео .build(); this.webClient = WebClient.builder() @@ -57,10 +54,23 @@ public class GeminiVideoGenerationService { } public byte[] generateVideo(String prompt) { + if (projectId == null || projectId.trim().isEmpty()) { + logger.error("VEO: Не указан Project ID."); + return null; + } + try { - // ШАГ 1: Запускаем задачу - String endpointUrl = String.format("%s/projects/%s/locations/%s/publishers/google/models/%s:predictLongRunning", - String.format(VERTEX_API_BASE, location), projectId, location, model); + String accessToken = getAccessTokenFromResources(); + if (accessToken == null) { + logger.error("VEO: Ошибка токена доступа."); + return null; + } + + // ШАГ 1: Отправляем запрос на генерацию + String generateEndpoint = String.format( + "https://%s-aiplatform.googleapis.com/v1/projects/%s/locations/%s/publishers/google/models/%s:predictLongRunning", + location, projectId, location, model + ); Map requestBody = new HashMap<>(); List> instances = new ArrayList<>(); @@ -73,11 +83,11 @@ public class GeminiVideoGenerationService { parameters.put("aspectRatio", "9:16"); requestBody.put("parameters", parameters); - logger.info("VEO: Запускаем генерацию. Промпт: {}", prompt); + logger.info("VEO: Запускаем генерацию видео. Промпт: '{}'", prompt); Map initResponse = webClient.post() - .uri(URI.create(endpointUrl)) - .header("Authorization", "Bearer " + getAccessTokenFromResources()) + .uri(URI.create(generateEndpoint)) + .header("Authorization", "Bearer " + accessToken) .contentType(MediaType.APPLICATION_JSON) .bodyValue(requestBody) .retrieve() @@ -85,57 +95,55 @@ public class GeminiVideoGenerationService { .block(Duration.ofSeconds(30)); if (initResponse == null || !initResponse.containsKey("name")) { - logger.error("VEO: Ошибка! Не получен ID операции. Ответ: {}", initResponse); + logger.error("VEO: Не удалось получить имя операции от Google. Ответ: {}", initResponse); return null; } - // ШАГ 2: Берем сырой путь операции и собираем ссылку через v1beta1 - String rawOperationName = (String) initResponse.get("name"); - logger.info("VEO: Задача успешно принята Google. ID: {}", rawOperationName); + String operationName = (String) initResponse.get("name"); + logger.info("VEO: Задача принята сервером Google. ID операции: {}", operationName); - String operationUrl = String.format("%s/%s", String.format(VERTEX_API_BASE, location), rawOperationName); - logger.info("VEO: URL для проверки (v1beta1): {}", operationUrl); - - // ШАГ 3: Цикл опроса (Polling) + // ШАГ 2: Опрос статуса задачи каждые 15 секунд (до 15 минут) + // ИСПОЛЬЗУЕМ v1 ДЛЯ ОПРОСА СТАТУСА (Google починил этот эндпоинт) + String statusEndpoint = String.format("https://%s-aiplatform.googleapis.com/v1/%s", location, operationName); int attempts = 0; - int maxAttempts = 60; // Ждем до 15 минут + int maxAttempts = 60; // 60 * 15 сек = 15 минут while (attempts < maxAttempts) { Thread.sleep(15000); attempts++; - logger.info("VEO: Проверка статуса видео... (попытка {}/{})", attempts, maxAttempts); + logger.info("VEO: Проверка статуса ({} / {})...", attempts, maxAttempts); Map statusResponse = webClient.get() - .uri(URI.create(operationUrl)) - .header("Authorization", "Bearer " + getAccessTokenFromResources()) + .uri(URI.create(statusEndpoint)) + .header("Authorization", "Bearer " + getAccessTokenFromResources()) // Токен нужно обновлять .retrieve() .bodyToMono(new ParameterizedTypeReference>() {}) .block(Duration.ofSeconds(30)); if (statusResponse != null) { if (statusResponse.containsKey("error")) { - logger.error("VEO: Ошибка внутри рендеринга видео: {}", statusResponse.get("error")); + logger.error("VEO: Внутренняя ошибка генерации Google: {}", statusResponse.get("error")); return null; } Boolean isDone = (Boolean) statusResponse.get("done"); if (Boolean.TRUE.equals(isDone)) { - logger.info("VEO: 🔥 ВИДЕО ГОТОВО! Скачиваем результат..."); + logger.info("VEO: Видео успешно сгенерировано! Парсим ответ..."); @SuppressWarnings("unchecked") - Map responseObj = (Map) statusResponse.get("response"); - return extractVideoFromResponse(responseObj); + Map responseData = (Map) statusResponse.get("response"); + return extractVideoBytes(responseData); } } } - logger.error("VEO: Превышено время ожидания рендеринга (таймаут)!"); + logger.error("VEO: Превышено время ожидания рендеринга (15 минут)."); return null; } catch (WebClientResponseException e) { - logger.error("VEO API ERROR! Status: {}, Body: {}", e.getStatusCode(), e.getResponseBodyAsString()); + logger.error("VEO API Error! HTTP Status: {}. Ответ: {}", e.getStatusCode(), e.getResponseBodyAsString()); return null; } catch (Exception e) { - logger.error("VEO API Неизвестная ошибка: {}", e.getMessage(), e); + logger.error("VEO: Неизвестная ошибка: {}", e.getMessage(), e); return null; } } @@ -151,28 +159,25 @@ public class GeminiVideoGenerationService { return credentials.getAccessToken().getTokenValue(); } } catch (IOException e) { - logger.error("Ошибка ключа: {}", e.getMessage()); + logger.error("Ошибка чтения ключа: {}", e.getMessage()); return null; } } @SuppressWarnings("unchecked") - private byte[] extractVideoFromResponse(Map response) { - if (response == null) return null; + private byte[] extractVideoBytes(Map responseData) { + if (responseData == null) return null; try { - List> predictions = (List>) response.get("predictions"); + List> predictions = (List>) responseData.get("predictions"); if (predictions != null && !predictions.isEmpty()) { Map firstPrediction = predictions.get(0); String base64Video = (String) firstPrediction.get("bytesBase64Encoded"); - if (base64Video == null) { - base64Video = (String) firstPrediction.get("b64"); - } if (base64Video != null) { - return java.util.Base64.getDecoder().decode(base64Video); + return Base64.getDecoder().decode(base64Video); } } } catch (Exception e) { - logger.error("VEO: Ошибка извлечения Base64: {}", e.getMessage()); + logger.error("VEO: Ошибка парсинга Base64: {}", e.getMessage()); } return null; } diff --git a/src/main/java/kz/konturai/parser/service/MarketingStrategyV3Service.java b/src/main/java/kz/konturai/parser/service/MarketingStrategyV3Service.java index 349a190..088240e 100644 --- a/src/main/java/kz/konturai/parser/service/MarketingStrategyV3Service.java +++ b/src/main/java/kz/konturai/parser/service/MarketingStrategyV3Service.java @@ -422,6 +422,7 @@ public class MarketingStrategyV3Service { private void doGenerateVideo(MarketingStrategy.PostCalendarItem item, String businessContext) { String videoPrompt = "High quality cinematic commercial video. Business niche: " + businessContext + ". Scene: " + item.getTheme() + ". Photorealistic, dynamic motion, 4k."; try { + log.info("Starting video generation process for theme: {}", item.getTheme()); byte[] videoBytes = geminiVideoService.generateVideo(videoPrompt); if (videoBytes != null && videoBytes.length > 0) { String filename = "video_" + System.currentTimeMillis() + "_" + item.hashCode() + ".mp4"; @@ -430,13 +431,14 @@ public class MarketingStrategyV3Service { item.setVideoFilename(filename); item.setImageUrl(null); item.setImageFilename(null); + log.info("Video successfully generated and uploaded as {}", filename); } else { log.error("Video generation returned null for theme: {}", item.getTheme()); item.setVideoFilename(null); item.setVideoUrl(null); } } catch (Exception e) { - log.error("Error generating video: {}", e.getMessage()); + log.error("Error during doGenerateVideo execution: {}", e.getMessage(), e); item.setVideoFilename(null); item.setVideoUrl(null); }