From db460d347bf7bef1baf79a2a043c73a69bfc5b77 Mon Sep 17 00:00:00 2001 From: Michal Wolski Date: Sun, 27 Apr 2025 21:31:03 +0200 Subject: [PATCH] Convert subtitles from MX Player Intents API to UTF-8 --- .../com/brouken/player/PlayerActivity.java | 32 +++-- .../com/brouken/player/SubtitleConverter.java | 122 ++++++++++++++++++ .../com/brouken/player/SubtitleUtils.java | 2 +- .../main/java/com/brouken/player/Utils.java | 11 +- 4 files changed, 151 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/brouken/player/SubtitleConverter.java diff --git a/app/src/main/java/com/brouken/player/PlayerActivity.java b/app/src/main/java/com/brouken/player/PlayerActivity.java index f66856e4..6cad22a2 100644 --- a/app/src/main/java/com/brouken/player/PlayerActivity.java +++ b/app/src/main/java/com/brouken/player/PlayerActivity.java @@ -100,10 +100,12 @@ import com.getkeepsafe.taptargetview.TapTarget; import com.getkeepsafe.taptargetview.TapTargetView; import com.google.android.material.snackbar.Snackbar; +import com.google.common.collect.Lists; import java.io.File; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -289,17 +291,25 @@ protected void onCreate(Bundle savedInstanceState) { defaultSub = (Uri) subsEnable[0]; } - Parcelable[] subs = bundle.getParcelableArray(API_SUBS); + Parcelable[] subParcelableArray = bundle.getParcelableArray(API_SUBS); + if (subParcelableArray == null) subParcelableArray = new Parcelable[0]; + + List subUriList = new ArrayList<>(subParcelableArray.length); + for (Parcelable parcelable : subParcelableArray) { + Uri element = (Uri) parcelable; + subUriList.add(element); + } + + List subs = new SubtitleConverter().convertSubtitles(this, subUriList); String[] subsName = bundle.getStringArray(API_SUBS_NAME); - if (subs != null && subs.length > 0) { - for (int i = 0; i < subs.length; i++) { - Uri sub = (Uri) subs[i]; - String name = null; - if (subsName != null && subsName.length > i) { - name = subsName[i]; - } - apiSubs.add(SubtitleUtils.buildSubtitle(this, sub, name, sub.equals(defaultSub))); + + for (int i = 0; i < subs.size(); i++) { + Uri sub = subs.get(i); + String name = null; + if (subsName != null && subsName.length > i) { + name = subsName[i]; } + apiSubs.add(SubtitleUtils.buildSubtitle(this, sub, name, sub.equals(defaultSub))); } } @@ -1297,7 +1307,7 @@ public void initializePlayer() { if (apiTitle != null) { title = apiTitle; } else { - title = Utils.getFileName(PlayerActivity.this, mPrefs.mediaUri); + title = Utils.getFileName(PlayerActivity.this, mPrefs.mediaUri, false); } if (title != null) { final MediaMetadata mediaMetadata = new MediaMetadata.Builder() @@ -1336,7 +1346,7 @@ public void initializePlayer() { if (apiTitle != null) { titleView.setText(apiTitle); } else { - titleView.setText(Utils.getFileName(this, mPrefs.mediaUri)); + titleView.setText(Utils.getFileName(this, mPrefs.mediaUri, false)); } titleView.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/com/brouken/player/SubtitleConverter.java b/app/src/main/java/com/brouken/player/SubtitleConverter.java new file mode 100644 index 00000000..909a8583 --- /dev/null +++ b/app/src/main/java/com/brouken/player/SubtitleConverter.java @@ -0,0 +1,122 @@ +package com.brouken.player; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; + +import com.sigpwned.chardet4j.Chardet; +import com.sigpwned.chardet4j.io.DecodedInputStreamReader; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class SubtitleConverter { + + private volatile OkHttpClient okHttpClient; + + public List convertSubtitles(Context context, List uris) { + CountDownLatch countDownLatch = new CountDownLatch(uris.size()); + Uri[] results = new Uri[uris.size()]; + + for (int i = 0; i < uris.size(); i++) { + convertSubtitle(context, countDownLatch, results, i, uris.get(i)); + } + + try { + countDownLatch.await(); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + + return Arrays.asList(results); + } + + private void convertSubtitle(Context context, CountDownLatch countDownLatch, Uri[] results, int positionOnResults, Uri sourceUri) { + String scheme = sourceUri.getScheme(); + + if (scheme == null) { + results[positionOnResults] = sourceUri; + countDownLatch.countDown(); + return; + } + + if (scheme.equals("http") || scheme.equals("https")) { + convertSubtitleFromHttp(context, countDownLatch, results, positionOnResults, sourceUri); + } else { + results[positionOnResults] = sourceUri; + } + } + + private void convertSubtitleFromHttp(Context context, CountDownLatch countDownLatch, Uri[] results, int positionOnResults, Uri sourceUri) { + new Thread(() -> { + OkHttpClient client = getOrCreateOkHttpClient(); + + Request request = new Request.Builder() + .url(sourceUri.toString()) + .build(); + + Uri convertedUri = sourceUri; + + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful()) { + ResponseBody responseBody = response.body(); + //noinspection DataFlowIssue + try (DecodedInputStreamReader reader = Chardet.decode(responseBody.byteStream(), StandardCharsets.UTF_8)) { + File subtitleCacheDir = getSubtitleCacheDir(context); + String fileName = Utils.getFileName(context, sourceUri, true); + File subtitleFile = new File(subtitleCacheDir, fileName); + try (Writer writer = new FileWriter(subtitleFile)) { + char[] buffer = new char[4096]; + int read; + while ((read = reader.read(buffer)) != -1) { + writer.write(buffer, 0, read); + } + writer.flush(); + convertedUri = Uri.fromFile(subtitleFile); + } + } + } + } catch (IOException e) { + Log.w(Utils.TAG, e); + } + + results[positionOnResults] = convertedUri; + countDownLatch.countDown(); + + + }).start(); + } + + private OkHttpClient getOrCreateOkHttpClient() { + if (okHttpClient == null) { + synchronized (this) { + if (okHttpClient == null) { + okHttpClient = new OkHttpClient.Builder().build(); + } + } + } + return okHttpClient; + } + + private static synchronized File getSubtitleCacheDir(Context context) throws IOException { + File subtitleCacheDir = new File(context.getCacheDir(), "subtitles"); + if (!subtitleCacheDir.exists()) { + if (!subtitleCacheDir.mkdirs()) { + throw new IOException("Couldn't create subtitles cache directory"); + } + } + return subtitleCacheDir; + } + +} diff --git a/app/src/main/java/com/brouken/player/SubtitleUtils.java b/app/src/main/java/com/brouken/player/SubtitleUtils.java index c28af301..1678835d 100644 --- a/app/src/main/java/com/brouken/player/SubtitleUtils.java +++ b/app/src/main/java/com/brouken/player/SubtitleUtils.java @@ -269,7 +269,7 @@ public static MediaItem.SubtitleConfiguration buildSubtitle(Context context, Uri final String subtitleMime = SubtitleUtils.getSubtitleMime(uri); final String subtitleLanguage = SubtitleUtils.getSubtitleLanguage(uri); if (subtitleLanguage == null && subtitleName == null) - subtitleName = Utils.getFileName(context, uri); + subtitleName = Utils.getFileName(context, uri, false); MediaItem.SubtitleConfiguration.Builder subtitleConfigurationBuilder = new MediaItem.SubtitleConfiguration.Builder(uri) .setMimeType(subtitleMime) diff --git a/app/src/main/java/com/brouken/player/Utils.java b/app/src/main/java/com/brouken/player/Utils.java index 7630dc3d..7c85c7e0 100644 --- a/app/src/main/java/com/brouken/player/Utils.java +++ b/app/src/main/java/com/brouken/player/Utils.java @@ -71,6 +71,8 @@ class Utils { + public static final String TAG = "JustPlayer"; + public static final String FEATURE_FIRE_TV = "amazon.hardware.fire_tv"; public static final String[] supportedExtensionsVideo = new String[] { "3gp", "m4v", "mkv", "mov", "mp4", "ts", "webm" }; @@ -155,7 +157,7 @@ public static void toggleSystemUi(final Activity activity, final CustomPlayerVie } } - public static String getFileName(Context context, Uri uri) { + public static String getFileName(Context context, Uri uri, boolean keepExtension) { String result = null; try { if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { @@ -174,10 +176,11 @@ public static String getFileName(Context context, Uri uri) { result = result.substring(cut + 1); } } - if (result.indexOf(".") > 0) - result = result.substring(0, result.lastIndexOf(".")); + if (!keepExtension) { + if (result.indexOf(".") > 0) result = result.substring(0, result.lastIndexOf(".")); + } } catch (Exception e) { - e.printStackTrace(); + Log.w(TAG, e); } return result; }