From c1de68893c868f2e2264a9adc300beeaf62e2c11 Mon Sep 17 00:00:00 2001
From: aliveoutside <53598473+aliveoutside@users.noreply.github.com>
Date: Fri, 16 Feb 2024 21:23:57 +0300
Subject: [PATCH] implement id3v2.3 tagging
---
app/build.gradle | 2 +
.../ru/vtosters/hooks/other/Preferences.java | 8 +++
.../lite/music/downloader/ID3Tagger.java | 55 +++++++++++++++++++
.../lite/music/downloader/M3UDownloader.java | 9 +++
.../lite/ui/fragments/MediaFragment.java | 35 ++++++++++++
smali/res/xml/preferences_media.xml | 3 +
6 files changed, 112 insertions(+)
create mode 100644 app/src/main/java/ru/vtosters/lite/music/downloader/ID3Tagger.java
diff --git a/app/build.gradle b/app/build.gradle
index c1b2a4af1..e526dfe86 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -97,6 +97,8 @@ dependencies {
implementation 'net.sourceforge.streamsupport:streamsupport-minifuture:1.7.4'
implementation 'com.google.guava:guava:32.0.0-android'
+ implementation 'com.mpatric:mp3agic:0.9.1'
+
implementation fileTree(dir: 'libs', include: ['*.aar'])
compileOnly fileTree(dir: 'libs', include: ['*.jar'])
}
diff --git a/app/src/main/java/ru/vtosters/hooks/other/Preferences.java b/app/src/main/java/ru/vtosters/hooks/other/Preferences.java
index ee621216a..74e76b994 100644
--- a/app/src/main/java/ru/vtosters/hooks/other/Preferences.java
+++ b/app/src/main/java/ru/vtosters/hooks/other/Preferences.java
@@ -355,4 +355,12 @@ public class Preferences {
if (MediaImageEncoder.needToSkipCompression()) return 100;
return origquality;
}
+
+ public static String metadataSeparator() {
+ return getPreferences().getString("metadata_separator", "; ");
+ }
+
+ public static void setMetadataSeparator(String separator) {
+ getPreferences().edit().putString("metadata_separator", separator).apply();
+ }
}
diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/ID3Tagger.java b/app/src/main/java/ru/vtosters/lite/music/downloader/ID3Tagger.java
new file mode 100644
index 000000000..f7c47e58b
--- /dev/null
+++ b/app/src/main/java/ru/vtosters/lite/music/downloader/ID3Tagger.java
@@ -0,0 +1,55 @@
+package ru.vtosters.lite.music.downloader;
+
+import android.util.Log;
+
+import com.mpatric.mp3agic.ID3v23Tag;
+import com.mpatric.mp3agic.InvalidDataException;
+import com.mpatric.mp3agic.Mp3File;
+import com.mpatric.mp3agic.NotSupportedException;
+import com.mpatric.mp3agic.UnsupportedTagException;
+import com.vk.dto.music.MusicTrack;
+import com.vk.dto.music.Artist;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.stream.Collectors;
+
+import ru.vtosters.hooks.other.Preferences;
+
+public class ID3Tagger {
+ public static void tag(File file, MusicTrack track) {
+ try {
+ // https://github.com/mpatric/mp3agic/issues/69#issuecomment-57897874
+ var oldFile = new File(file.getAbsolutePath().replace(".mp3", "_old.mp3"));
+ if (!file.renameTo(oldFile)) {
+ Log.e("ID3Tagger", "Failed to rename file");
+ return;
+ }
+
+ var mp3File = new Mp3File(oldFile);
+ if (mp3File.hasId3v2Tag()) {
+ var id3v2Tag = new ID3v23Tag();
+
+ if (track.L == null || track.I == null) {
+ id3v2Tag.setArtist(track.C);
+ } else {
+ var artists = track.L.stream().map(Artist::w1).collect(Collectors.joining(Preferences.metadataSeparator()));
+ id3v2Tag.setArtist(artists);
+ id3v2Tag.setAlbum(track.I.getTitle());
+ }
+
+ id3v2Tag.setTitle(M3UDownloader.getTitle(track));
+
+ mp3File.setId3v2Tag(id3v2Tag);
+ mp3File.save(file.getAbsolutePath());
+
+ if (!oldFile.delete()) {
+ Log.e("ID3Tagger", "Failed to delete file");
+ }
+ }
+ } catch (IOException | UnsupportedTagException | InvalidDataException |
+ NotSupportedException e) {
+ Log.e("ID3Tagger", "Failed to tag file", e);
+ }
+ }
+}
diff --git a/app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java b/app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java
index 5f03ca33a..3e449f739 100644
--- a/app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java
+++ b/app/src/main/java/ru/vtosters/lite/music/downloader/M3UDownloader.java
@@ -6,9 +6,13 @@ import android.util.Log;
import com.google.android.exoplayer2.source.hls.playlist.e;
import com.google.android.exoplayer2.source.hls.playlist.f;
import com.google.android.exoplayer2.source.hls.playlist.f.a;
+import com.mpatric.mp3agic.InvalidDataException;
+import com.mpatric.mp3agic.Mp3File;
+import com.mpatric.mp3agic.UnsupportedTagException;
import com.vk.dto.music.MusicTrack;
import java8.util.concurrent.CompletableFuture;
import java8.util.concurrent.CompletionException;
+import ru.vtosters.hooks.other.Preferences;
import ru.vtosters.lite.music.cache.MusicCacheImpl;
import ru.vtosters.lite.music.converter.ts.MpegDemuxer;
import ru.vtosters.lite.music.converter.ts.TSMerger;
@@ -72,6 +76,11 @@ public class M3UDownloader
}
})
.thenRun(() -> callback.onProgress(10 + Math.round(80.0f * progress.addAndGet(1) / segments.size())))
+ .thenRun(() -> {
+ if (Preferences.getBoolValue("setMetaData", true)) {
+ ID3Tagger.tag(resultMp3, track);
+ }
+ })
.thenRun(() -> {
if (cache) {
MusicCacheImpl.addTrack(track);
diff --git a/app/src/main/java/ru/vtosters/lite/ui/fragments/MediaFragment.java b/app/src/main/java/ru/vtosters/lite/ui/fragments/MediaFragment.java
index 21222afe7..726cc297e 100644
--- a/app/src/main/java/ru/vtosters/lite/ui/fragments/MediaFragment.java
+++ b/app/src/main/java/ru/vtosters/lite/ui/fragments/MediaFragment.java
@@ -162,6 +162,41 @@ public class MediaFragment extends TrackedMaterialPreferenceToolbarFragment {
});
findPreference("maxquality").setEnabled(Preferences.isEnableExternalOpening());
+
+ findPreference("metadataSeparator").setOnPreferenceClickListener(preference -> {
+ LinearLayout linearLayout = new LinearLayout(requireContext());
+ linearLayout.setOrientation(LinearLayout.VERTICAL);
+
+
+ EditText separator = new EditText(requireContext());
+ separator.setHint("По-умолчанию: ; с пробелом");
+ separator.setTextColor(ThemesUtils.getTextAttr());
+ separator.setHintTextColor(ThemesUtils.getSTextAttr());
+ separator.setBackgroundTintList(ThemesUtils.getAccenedColorStateList());
+ separator.setText(Preferences.metadataSeparator());
+ linearLayout.addView(separator);
+ separator.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
+ ViewGroup.MarginLayoutParams margin = ((ViewGroup.MarginLayoutParams) separator.getLayoutParams());
+ margin.setMargins(AndroidUtils.dp2px(20f), 0, AndroidUtils.dp2px(20f), 0);
+ separator.setLayoutParams(margin);
+
+
+ var t = new VkAlertDialog.Builder(requireContext())
+ .setTitle("Разделитель")
+ .setView(linearLayout)
+ .setPositiveButton("Сохранить", (dialog, which) -> {
+ if (separator.getText().toString().isEmpty()) {
+ AndroidUtils.sendToast("Разделитель не может быть пустым");
+ return;
+ }
+ Preferences.setMetadataSeparator(separator.getText().toString());
+ })
+ .setNegativeButton("Отмена", (dialog, which) -> dialog.cancel())
+ .show();
+
+
+ return true;
+ });
}
private void lastfmAuth(Context ctx) {
diff --git a/smali/res/xml/preferences_media.xml b/smali/res/xml/preferences_media.xml
index 738dcd4a3..761ab0ef5 100644
--- a/smali/res/xml/preferences_media.xml
+++ b/smali/res/xml/preferences_media.xml
@@ -55,6 +55,9 @@
android:key="setMetaData"
android:summary="Сохранять id3v2 теги для песен для MP3"
android:title="Сохранять метадату песен"/>
+