Skip to content

Commit 917d02d

Browse files
committed
feat(YouTube - Overlay buttons): Add Gemini Transcription on long press & add Gemini Summary to Shorts custom actions
Close #1022
1 parent 41dd3b4 commit 917d02d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2407
-709
lines changed
Lines changed: 23 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -1,206 +1,64 @@
11
package app.revanced.extension.youtube.patches.overlaybutton;
22

3-
import static app.revanced.extension.shared.utils.StringRef.str;
4-
import static app.revanced.extension.shared.utils.Utils.showToastLong;
5-
import static app.revanced.extension.shared.utils.Utils.showToastShort;
6-
7-
import android.app.AlertDialog;
83
import android.content.Context;
9-
import android.os.Build;
10-
import android.os.Handler;
11-
import android.os.Looper;
12-
import android.text.TextUtils;
134
import android.view.View;
145
import android.view.ViewGroup;
15-
166
import androidx.annotation.Nullable;
17-
import androidx.annotation.RequiresApi;
18-
197
import app.revanced.extension.shared.utils.Logger;
208
import app.revanced.extension.youtube.settings.Settings;
21-
import app.revanced.extension.youtube.utils.GeminiUtils;
9+
import app.revanced.extension.youtube.utils.GeminiManager;
2210
import app.revanced.extension.youtube.utils.VideoUtils;
2311

2412
@SuppressWarnings("unused")
2513
public class GeminiSummarize extends BottomControlButton {
2614
@Nullable
2715
private static GeminiSummarize instance;
28-
@Nullable
29-
private AlertDialog progressDialog;
30-
private boolean isCancelled = false;
31-
32-
// Timer related variables
33-
private final Handler timerHandler;
34-
private Runnable timerRunnable;
35-
private long startTimeMillis;
36-
private String baseLoadingMessage;
37-
private int totalSummarizationTimeSeconds = -1;
3816

3917
public GeminiSummarize(ViewGroup bottomControlsViewGroup) {
4018
super(
4119
bottomControlsViewGroup,
4220
"gemini_summarize_button",
4321
Settings.OVERLAY_BUTTON_GEMINI_SUMMARIZE,
4422
view -> handleSummarizeClick(view.getContext()),
45-
null
23+
view -> {
24+
handleTranscribeClick(view.getContext());
25+
return true;
26+
}
4627
);
47-
timerHandler = new Handler(Looper.getMainLooper());
4828
}
4929

50-
// region Generate Summary
51-
private static void handleSummarizeClick(Context context) {
52-
if (instance == null) return;
53-
54-
final String apiKey = Settings.GEMINI_API_KEY.get();
55-
if (TextUtils.isEmpty(apiKey)) {
56-
showToastLong(str("revanced_gemini_error_no_api_key"));
30+
public static void handleSummarizeClick(Context context) {
31+
if (instance == null) {
32+
Logger.printException(() -> "GeminiSummarize button instance is null, cannot proceed with summarize.");
5733
return;
5834
}
59-
6035
final String videoUrl = VideoUtils.getVideoUrl(false);
61-
if (TextUtils.isEmpty(videoUrl) || videoUrl.equals(VideoUtils.VIDEO_URL)) {
62-
showToastShort(str("revanced_gemini_error_no_video"));
63-
return;
64-
}
65-
66-
instance.isCancelled = false;
67-
instance.totalSummarizationTimeSeconds = -1;
68-
instance.showProgressDialog(context);
69-
70-
GeminiUtils.getVideoSummary(videoUrl, apiKey, new GeminiUtils.SummaryCallback() {
71-
@RequiresApi(api = Build.VERSION_CODES.P)
72-
@Override
73-
public void onSuccess(String summary) {
74-
context.getMainExecutor().execute(() -> {
75-
if (instance == null || instance.isCancelled) {
76-
Logger.printDebug(() -> "Gemini request succeeded but was cancelled by user.");
77-
instance.dismissProgressDialog();
78-
return;
79-
}
80-
// Calculate total time *before* dismissing progress dialog
81-
long endTimeMillis = System.currentTimeMillis();
82-
if (instance.startTimeMillis > 0) {
83-
long elapsedMillis = endTimeMillis - instance.startTimeMillis;
84-
instance.totalSummarizationTimeSeconds = (int) (elapsedMillis / 1000);
85-
}
86-
87-
instance.dismissProgressDialog();
88-
instance.showSummaryDialog(context, summary, instance.totalSummarizationTimeSeconds);
89-
});
90-
}
91-
92-
@RequiresApi(api = Build.VERSION_CODES.P)
93-
@Override
94-
public void onFailure(String error) {
95-
context.getMainExecutor().execute(() -> {
96-
if (instance == null || instance.isCancelled) {
97-
Logger.printDebug(() -> "Gemini request failed but was cancelled by user.");
98-
instance.dismissProgressDialog();
99-
return;
100-
}
101-
instance.dismissProgressDialog();
102-
showToastLong(str("revanced_gemini_error_api_failed", error));
103-
});
104-
}
105-
});
106-
}
107-
108-
private void showProgressDialog(Context context) {
109-
stopTimer();
110-
dismissProgressDialog();
111-
112-
baseLoadingMessage = str("revanced_gemini_loading");
113-
startTimeMillis = -1; // Reset start time until timer actually starts
114-
115-
AlertDialog.Builder builder = new AlertDialog.Builder(context);
116-
// Set initial message without time
117-
builder.setMessage(baseLoadingMessage);
118-
builder.setCancelable(false);
119-
120-
builder.setNegativeButton(str("revanced_cancel"), (dialog, which) -> {
121-
isCancelled = true;
122-
stopTimer();
123-
dismissProgressDialog();
124-
showToastShort(str("revanced_gemini_cancelled"));
125-
Logger.printDebug(() -> "Gemini summarization cancelled by user.");
126-
});
127-
128-
progressDialog = builder.create();
129-
progressDialog.show();
130-
131-
// Start the timer which will update the message
132-
startTimer();
133-
}
134-
135-
private void dismissProgressDialog() {
136-
stopTimer();
137-
if (progressDialog != null && progressDialog.isShowing()) {
138-
try {
139-
progressDialog.dismiss();
140-
} catch (IllegalArgumentException e) {
141-
Logger.printException(() -> "Error dismissing progress dialog", e);
142-
}
143-
}
144-
progressDialog = null;
36+
GeminiManager.getInstance().startSummarization(context, videoUrl);
14537
}
14638

147-
private void showSummaryDialog(Context context, String summary, int secondsTaken) {
148-
dismissProgressDialog();
149-
150-
String finalMessage = summary;
151-
if (secondsTaken >= 0) {
152-
finalMessage += "\n\n" + str("revanced_gemini_time_taken", secondsTaken);
39+
public static void handleTranscribeClick(Context context) {
40+
if (instance == null) {
41+
Logger.printException(() -> "GeminiSummarize button instance is null, cannot proceed with transcribe.");
42+
return;
15343
}
44+
final String videoUrl = VideoUtils.getVideoUrl(false);
15445

155-
new AlertDialog.Builder(context)
156-
.setTitle(str("revanced_gemini_summary_title"))
157-
.setMessage(finalMessage)
158-
.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
159-
.setNeutralButton(str("revanced_copy"), (dialog, which) -> {
160-
// Copy only the summary part, not the time taken text
161-
VideoUtils.setClipboard(summary, str("revanced_gemini_copy_success"));
162-
})
163-
.show();
164-
}
165-
// endregion Generate Summary
166-
167-
// region Timer
168-
private void startTimer() {
169-
startTimeMillis = System.currentTimeMillis();
170-
timerRunnable = new Runnable() {
171-
@Override
172-
public void run() {
173-
if (progressDialog != null && progressDialog.isShowing() && !isCancelled) {
174-
long elapsedMillis = System.currentTimeMillis() - startTimeMillis;
175-
int elapsedSeconds = (int) (elapsedMillis / 1000);
176-
177-
String timeString = elapsedSeconds + "s";
178-
progressDialog.setMessage(baseLoadingMessage + "\n" + timeString);
179-
180-
timerHandler.postDelayed(this, 1000);
181-
}
182-
}
183-
};
184-
// Start the first update slightly delayed to ensure dialog is visible
185-
timerHandler.postDelayed(timerRunnable, 500);
186-
}
187-
188-
private void stopTimer() {
189-
if (timerHandler != null && timerRunnable != null) {
190-
timerHandler.removeCallbacks(timerRunnable);
191-
}
192-
// Don't reset startTimeMillis here, we need it in onSuccess
46+
GeminiManager.getInstance().startTranscription(context, videoUrl);
19347
}
194-
// endregion Timer
19548

196-
// region Lifecycle and Visibility
19749
public static void initialize(View bottomControlsViewGroup) {
19850
try {
19951
if (bottomControlsViewGroup instanceof ViewGroup viewGroup) {
200-
instance = new GeminiSummarize(viewGroup);
52+
if (instance == null) {
53+
instance = new GeminiSummarize(viewGroup);
54+
Logger.printDebug(() -> "GeminiSummarize button initialized (with long click).");
55+
} else {
56+
Logger.printInfo(() -> "GeminiSummarize initialize called multiple times.");
57+
}
20158
}
20259
} catch (Exception ex) {
203-
Logger.printException(() -> "initialize failure", ex);
60+
Logger.printException(() -> "GeminiSummarize.initialize failure", ex);
61+
instance = null;
20462
}
20563
}
20664

@@ -211,5 +69,4 @@ public static void changeVisibility(boolean showing, boolean animation) {
21169
public static void changeVisibilityNegatedImmediate() {
21270
if (instance != null) instance.setVisibilityNegatedImmediate();
21371
}
214-
// endregion Lifecycle and Visibility
21572
}

extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/CustomActionsPatch.java

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,38 @@
11
package app.revanced.extension.youtube.patches.shorts;
22

3-
import static app.revanced.extension.shared.utils.ResourceUtils.getString;
4-
import static app.revanced.extension.youtube.patches.components.ShortsCustomActionsFilter.isShortsFlyoutMenuVisible;
5-
import static app.revanced.extension.youtube.shared.RootView.isShortsActive;
6-
import static app.revanced.extension.youtube.utils.ExtendedUtils.isSpoofingToLessThan;
7-
83
import android.content.Context;
94
import android.graphics.drawable.Drawable;
105
import android.support.v7.widget.RecyclerView;
6+
import android.text.TextUtils;
117
import android.view.View;
128
import android.view.ViewGroup;
139
import android.widget.ImageView;
1410
import android.widget.LinearLayout;
1511
import android.widget.ScrollView;
1612
import android.widget.TextView;
17-
1813
import androidx.annotation.NonNull;
1914
import androidx.annotation.Nullable;
20-
21-
import org.apache.commons.lang3.StringUtils;
22-
23-
import java.lang.ref.WeakReference;
24-
import java.util.LinkedHashMap;
25-
import java.util.Map;
26-
import java.util.Objects;
27-
2815
import app.revanced.extension.shared.settings.BooleanSetting;
2916
import app.revanced.extension.shared.utils.Logger;
3017
import app.revanced.extension.shared.utils.ResourceUtils;
3118
import app.revanced.extension.shared.utils.Utils;
3219
import app.revanced.extension.youtube.patches.components.ShortsCustomActionsFilter;
3320
import app.revanced.extension.youtube.settings.Settings;
3421
import app.revanced.extension.youtube.utils.ExtendedUtils;
22+
import app.revanced.extension.youtube.utils.GeminiManager;
3523
import app.revanced.extension.youtube.utils.VideoUtils;
24+
import org.apache.commons.lang3.StringUtils;
25+
26+
import java.lang.ref.WeakReference;
27+
import java.util.LinkedHashMap;
28+
import java.util.Map;
29+
import java.util.Objects;
30+
31+
import static app.revanced.extension.shared.utils.ResourceUtils.getString;
32+
import static app.revanced.extension.shared.utils.StringRef.str;
33+
import static app.revanced.extension.youtube.patches.components.ShortsCustomActionsFilter.isShortsFlyoutMenuVisible;
34+
import static app.revanced.extension.youtube.shared.RootView.isShortsActive;
35+
import static app.revanced.extension.youtube.utils.ExtendedUtils.isSpoofingToLessThan;
3636

3737
@SuppressWarnings("unused")
3838
public final class CustomActionsPatch {
@@ -330,6 +330,30 @@ public enum CustomAction {
330330
"yt_outline_play_arrow_half_circle_black_24",
331331
() -> VideoUtils.showPlaybackSpeedDialog(contextRef.get())
332332
),
333+
GEMINI(
334+
Settings.SHORTS_CUSTOM_ACTIONS_GEMINI,
335+
"revanced_gemini_button",
336+
() -> {
337+
Context context = contextRef.get();
338+
339+
String shortsVideoId = ShortsCustomActionsFilter.getShortsVideoId();
340+
String videoUrl;
341+
if (!TextUtils.isEmpty(shortsVideoId)) {
342+
videoUrl = VideoUtils.getVideoUrl(shortsVideoId, false);
343+
} else {
344+
// Fallback to general video URL if shorts ID not found (might be less reliable in shorts)
345+
videoUrl = VideoUtils.getVideoUrl(false);
346+
Logger.printInfo(() -> "GEMINI CustomAction: Could not get Shorts specific Video ID, using general VideoUtils.");
347+
}
348+
349+
if (TextUtils.isEmpty(videoUrl) || videoUrl.equals(VideoUtils.VIDEO_URL)) {
350+
Utils.showToastShort(str("revanced_gemini_error_no_video"));
351+
return;
352+
}
353+
354+
GeminiManager.getInstance().startSummarization(context, videoUrl);
355+
}
356+
),
333357
REPEAT_STATE(
334358
Settings.SHORTS_CUSTOM_ACTIONS_REPEAT_STATE,
335359
"yt_outline_arrow_repeat_1_black_24",

extensions/shared/src/main/java/app/revanced/extension/youtube/settings/Settings.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ public class Settings extends BaseSettings {
408408
public static final BooleanSetting OVERLAY_BUTTON_WHITELIST = new BooleanSetting("revanced_overlay_button_whitelist", FALSE);
409409
public static final BooleanSetting OVERLAY_BUTTON_GEMINI_SUMMARIZE = new BooleanSetting("revanced_overlay_button_gemini_summarize", FALSE);
410410
public static final StringSetting GEMINI_API_KEY = new StringSetting("revanced_overlay_button_gemini_summarize_api_key", "", true, parent(OVERLAY_BUTTON_GEMINI_SUMMARIZE));
411+
public static final IntegerSetting GEMINI_TRANSCRIBE_SUBTITLES_FONT_SIZE = new IntegerSetting("revanced_gemini_transcribe_subtitles_font_size", 14, true, parent(OVERLAY_BUTTON_GEMINI_SUMMARIZE));
411412

412413
// PreferenceScreen: Player - Seekbar
413414
public static final BooleanSetting APPEND_TIME_STAMP_INFORMATION = new BooleanSetting("revanced_append_time_stamp_information", TRUE, true);
@@ -504,6 +505,7 @@ public class Settings extends BaseSettings {
504505
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_shorts_custom_actions_external_downloader", FALSE, true);
505506
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_OPEN_VIDEO = new BooleanSetting("revanced_shorts_custom_actions_open_video", FALSE, true);
506507
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_SPEED_DIALOG = new BooleanSetting("revanced_shorts_custom_actions_speed_dialog", FALSE, true);
508+
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_GEMINI = new BooleanSetting("revanced_shorts_custom_actions_gemini", FALSE, true);
507509
public static final BooleanSetting SHORTS_CUSTOM_ACTIONS_REPEAT_STATE = new BooleanSetting("revanced_shorts_custom_actions_repeat_state", FALSE, true);
508510

509511
public static final BooleanSetting ENABLE_SHORTS_CUSTOM_ACTIONS_FLYOUT_MENU = new BooleanSetting("revanced_enable_shorts_custom_actions_flyout_menu", FALSE, true,

0 commit comments

Comments
 (0)