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="Сохранять метадату песен"/> +