fix
This commit is contained in:
@@ -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<String, Object> requestBody = new HashMap<>();
|
||||
List<Map<String, Object>> 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<String, Object> 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<String, Object> statusResponse = webClient.get()
|
||||
.uri(URI.create(operationUrl))
|
||||
.header("Authorization", "Bearer " + getAccessTokenFromResources())
|
||||
.uri(URI.create(statusEndpoint))
|
||||
.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"));
|
||||
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<String, Object> responseObj = (Map<String, Object>) statusResponse.get("response");
|
||||
return extractVideoFromResponse(responseObj);
|
||||
Map<String, Object> responseData = (Map<String, Object>) 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<String, Object> response) {
|
||||
if (response == null) return null;
|
||||
private byte[] extractVideoBytes(Map<String, Object> responseData) {
|
||||
if (responseData == null) return null;
|
||||
try {
|
||||
List<Map<String, Object>> predictions = (List<Map<String, Object>>) response.get("predictions");
|
||||
List<Map<String, Object>> predictions = (List<Map<String, Object>>) responseData.get("predictions");
|
||||
if (predictions != null && !predictions.isEmpty()) {
|
||||
Map<String, Object> 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user