From 88e54e64db3778c14a2d5cdecc6516632eae1875 Mon Sep 17 00:00:00 2001 From: Aaron Veil <70171475+anddea@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:20:40 +0300 Subject: [PATCH] feat(YouTube - Overlay buttons): Improve the translation workflow of subtitles after Yandex transcription --- .../youtube/utils/GeminiManager.java | 4 +- .../extension/youtube/utils/GeminiUtils.java | 30 +++++++++- .../youtube/utils/YandexVotUtils.java | 55 +++++++++++++++++++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/GeminiManager.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/GeminiManager.java index 03706acee..f5500c0b1 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/GeminiManager.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/GeminiManager.java @@ -374,8 +374,10 @@ public final class GeminiManager { } // Call Gemini for translation + String cleanedJson = YandexVotUtils.stripTokensFromYandexJson(rawIntermediateJson); + GeminiUtils.translateYandexJson( - rawIntermediateJson, + cleanedJson, targetLangName, apiKey, new GeminiUtils.Callback() { diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/GeminiUtils.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/GeminiUtils.java index b0de560a1..36bc6d589 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/GeminiUtils.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/GeminiUtils.java @@ -236,7 +236,19 @@ public class GeminiUtils { .getJSONArray("parts") .getJSONObject(0) .getString("text"); - final String finalResult = resultText.trim(); + + final String finalResult = sanitizeJsonOutput(resultText); + Logger.printDebug(() -> "Gemini RAW result (Sanitized): " + finalResult.substring(0, Math.min(finalResult.length(), 300)) + "..."); + + if (videoUrl == null) { + boolean looksLikeJson = finalResult.startsWith("[") || finalResult.startsWith("{"); + if (!looksLikeJson) { + Logger.printInfo(() -> "Gemini JSON translation result doesn't look like valid JSON!"); + Logger.printInfo(() -> "Start of content: " + finalResult.substring(0, Math.min(finalResult.length(), 50))); + mainThreadHandler.post(() -> callback.onFailure("Translation result format error. Expected JSON.")); + return; + } + } Logger.printDebug(() -> "Gemini RAW result received: " + finalResult.substring(0, Math.min(finalResult.length(), 300)) + "..."); @@ -498,6 +510,22 @@ public class GeminiUtils { return "English"; } + /** + * Cleans the Gemini response to remove potential Markdown formatting. + */ + private static String sanitizeJsonOutput(String text) { + if (text == null) return ""; + text = text.trim(); + + if (text.startsWith("```")) { + text = text.replaceFirst("^```[a-zA-Z]*\\s*", ""); + } + if (text.endsWith("```")) { + text = text.replaceAll("\\s*```$", ""); + } + return text.trim(); + } + /** * Callback interface for Gemini API operations (summary, transcription). * Defines methods to handle successful results or failures. diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/YandexVotUtils.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/YandexVotUtils.java index 1a2fb4adb..49bcd1bc0 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/YandexVotUtils.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/utils/YandexVotUtils.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import app.revanced.extension.shared.utils.Logger; import okhttp3.*; +import org.jetbrains.annotations.NotNull; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -973,6 +974,60 @@ public class YandexVotUtils { return serverMessage; } + /** + * Helper method to strip the "tokens" array from Yandex JSON. + * This drastically reduces the size of the JSON sent to Gemini, + * preventing output truncation and saving tokens. + * + * @param jsonContent The raw JSON from Yandex. + * @return The JSON string with 'tokens' arrays removed from each subtitle entry. + */ + public static String stripTokensFromYandexJson(String jsonContent) { + if (TextUtils.isEmpty(jsonContent)) return jsonContent; + try { + // Handle case where JSON is just an array (non-standard Yandex output) + if (jsonContent.trim().startsWith("[")) { + JSONArray root = new JSONArray(jsonContent); + for (int i = 0; i < root.length(); i++) { + JSONObject item = root.getJSONObject(i); + if (item.has("tokens")) item.remove("tokens"); + } + return root.toString(); + } else { + // Handle standard object with "subtitles" array (standard Yandex output) + JSONObject root = getJsonObject(jsonContent); + return root.toString(); + } + } catch (JSONException e) { + Logger.printException(() -> "Failed to strip tokens from JSON", e); + return jsonContent; // Return original if modification fails + } + } + + /** + * Parses the provided JSON string into a JSONObject and removes the verbose "tokens" + * arrays from within the "subtitles" list. It also updates metadata flags to reflect + * the removal of tokens. + * + * @param jsonContent The raw JSON string containing the subtitle object. + * @return The modified {@link JSONObject} with tokens removed. + * @throws JSONException If the string cannot be parsed or the structure is invalid. + */ + @NotNull + private static JSONObject getJsonObject(String jsonContent) throws JSONException { + JSONObject root = new JSONObject(jsonContent); + if (root.has("subtitles")) { + JSONArray subs = root.getJSONArray("subtitles"); + for (int i = 0; i < subs.length(); i++) { + JSONObject item = subs.getJSONObject(i); + if (item.has("tokens")) item.remove("tokens"); + } + } + // Also remove top-level containsTokens flag if present + if (root.has("containsTokens")) root.put("containsTokens", false); + return root; + } + // endregion Utils // region Interfaces and Inner Classes