From 52ada5c063c657d2636d33f751b67b61667e1742 Mon Sep 17 00:00:00 2001 From: devoxin <15076404+devoxin@users.noreply.github.com> Date: Sun, 5 May 2024 16:00:54 +0100 Subject: [PATCH] Fix handling of multiple MP3 IDv3 blocks, add basic Matroska metadata extraction (#108) * Add basic Matroska metadata extraction. * Read all IDv3 blocks in an MP3 file * inline while condition --- .../matroska/MatroskaContainerProbe.java | 8 ++- .../matroska/MatroskaStreamingFile.java | 57 +++++++++++++++++++ .../matroska/format/MatroskaElementType.java | 8 ++- .../container/mp3/Mp3TrackProvider.java | 52 +++++++++-------- 4 files changed, 98 insertions(+), 27 deletions(-) diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/MatroskaContainerProbe.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/MatroskaContainerProbe.java index 776b7d7b..b120e60a 100644 --- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/MatroskaContainerProbe.java +++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/MatroskaContainerProbe.java @@ -59,7 +59,13 @@ public MediaContainerDetectionResult probe(AudioReference reference, SeekableInp return unsupportedFormat(this, "No supported audio tracks present in the file."); } - return supportedFormat(this, null, new AudioTrackInfo(UNKNOWN_TITLE, UNKNOWN_ARTIST, + String title = file.getTitle(); + String actualTitle = title == null || title.isEmpty() ? UNKNOWN_TITLE : title; + + String artist = file.getArtist(); + String actualArtist = artist == null || artist.isEmpty() ? UNKNOWN_ARTIST : artist; + + return supportedFormat(this, null, new AudioTrackInfo(actualTitle, actualArtist, (long) file.getDuration(), reference.identifier, false, reference.identifier, null, null)); } diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/MatroskaStreamingFile.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/MatroskaStreamingFile.java index e9b555b3..6e660a52 100644 --- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/MatroskaStreamingFile.java +++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/MatroskaStreamingFile.java @@ -16,6 +16,9 @@ public class MatroskaStreamingFile { private final MatroskaFileReader reader; + private String title; + private String artist; + private long timecodeScale = 1000000; private double duration; private final ArrayList trackList = new ArrayList<>(); @@ -42,6 +45,17 @@ public long getTimecodeScale() { return timecodeScale; } + /** + * @return The title for this file. + */ + public String getTitle() { + return title; + } + + public String getArtist() { + return artist; + } + /** * @return Total duration of the file */ @@ -114,6 +128,8 @@ private void parseSegmentElement(MatroskaElement segmentElement) throws IOExcept while ((child = reader.readNextElement(segmentElement)) != null) { if (child.is(MatroskaElementType.Info)) { parseSegmentInfo(child); + } else if (child.is(MatroskaElementType.Tags)) { + parseTags(child); } else if (child.is(MatroskaElementType.Tracks)) { parseTracks(child); } else if (child.is(MatroskaElementType.Cluster)) { @@ -378,6 +394,8 @@ private void parseSegmentInfo(MatroskaElement infoElement) throws IOException { duration = reader.asDouble(child); } else if (child.is(MatroskaElementType.TimecodeScale)) { timecodeScale = reader.asLong(child); + } else if (child.is(MatroskaElementType.Title)) { + title = reader.asString(child); } reader.skip(child); @@ -395,4 +413,43 @@ private void parseTracks(MatroskaElement tracksElement) throws IOException { reader.skip(child); } } + + private void parseTags(MatroskaElement tagsElement) throws IOException { + MatroskaElement child; + + while ((child = reader.readNextElement(tagsElement)) != null) { + if (child.is(MatroskaElementType.Tag)) { + parseTag(child); + } + + reader.skip(child); + } + } + + private void parseTag(MatroskaElement tagElement) throws IOException { + MatroskaElement child; + + while ((child = reader.readNextElement(tagElement)) != null) { + if (child.is(MatroskaElementType.SimpleTag)) { + parseSimpleTag(child); + } + + reader.skip(child); + } + } + + private void parseSimpleTag(MatroskaElement simpleTagElement) throws IOException { + MatroskaElement child; + String tagName = null; + + while ((child = reader.readNextElement(simpleTagElement)) != null) { + if (child.is(MatroskaElementType.TagName)) { + tagName = reader.asString(child); + } else if (child.is(MatroskaElementType.TagString)) { + if ("artist".equalsIgnoreCase(tagName)) { + artist = reader.asString(child); + } + } + } + } } diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/format/MatroskaElementType.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/format/MatroskaElementType.java index 7fa4e6fd..620ce6cb 100644 --- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/format/MatroskaElementType.java +++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/matroska/format/MatroskaElementType.java @@ -17,6 +17,9 @@ public enum MatroskaElementType { SeekId(DataType.BINARY, new int[]{0x53, 0xAB}), SeekPosition(DataType.UNSIGNED_INTEGER, new int[]{0x53, 0xAC}), Info(DataType.MASTER, new int[]{0x15, 0x49, 0xA9, 0x66}), + Tags(DataType.MASTER, new int[] { 0x12, 0x54, 0xC3, 0x67 }), + Tag(DataType.MASTER, new int[] { 0x73, 0x73 }), + SimpleTag(DataType.MASTER, new int[] { 0x67, 0xC8 }), Duration(DataType.FLOAT, new int[]{0x44, 0x89}), TimecodeScale(DataType.UNSIGNED_INTEGER, new int[]{0x2A, 0xD7, 0xB1}), Cluster(DataType.MASTER, new int[]{0x1F, 0x43, 0xB6, 0x75}), @@ -45,9 +48,12 @@ public enum MatroskaElementType { CueTrackPositions(DataType.MASTER, new int[]{0xB7}), CueTrack(DataType.UNSIGNED_INTEGER, new int[]{0xF7}), CueClusterPosition(DataType.UNSIGNED_INTEGER, new int[]{0xF1}), + Title(DataType.STRING, new int[] { 0x7B, 0xA9 }), + TagName(DataType.STRING, new int[] { 0x45, 0xA3 }), + TagString(DataType.STRING, new int[] { 0x44, 0x87 }), Unknown(DataType.BINARY, new int[]{}); - private static Map mapping; + private static final Map mapping; /** * The ID as EBML code bytes. diff --git a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/mp3/Mp3TrackProvider.java b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/mp3/Mp3TrackProvider.java index bd46b586..dd5c855a 100644 --- a/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/mp3/Mp3TrackProvider.java +++ b/main/src/main/java/com/sedmelluq/discord/lavaplayer/container/mp3/Mp3TrackProvider.java @@ -117,11 +117,7 @@ private void initialiseSeeker() throws IOException { */ public void provideFrames() throws InterruptedException { try { - while (true) { - if (!frameReader.fillFrameBuffer()) { - break; - } - + while (frameReader.fillFrameBuffer()) { inputBuffer.clear(); inputBuffer.put(frameBuffer, 0, frameReader.getFrameSize()); inputBuffer.flip(); @@ -195,35 +191,41 @@ public void close() { } private void skipIdv3Tags() throws IOException { - dataInput.readFully(tagHeaderBuffer, 0, 3); + byte[] lastTagHeader = new byte[4]; - for (int i = 0; i < 3; i++) { - if (tagHeaderBuffer[i] != IDV3_TAG[i]) { - frameReader.appendToScanBuffer(tagHeaderBuffer, 0, 3); - return; + while (true) { + System.arraycopy(tagHeaderBuffer, 0, lastTagHeader, 0, 4); + dataInput.readFully(tagHeaderBuffer, 0, 3); + + for (int i = 0; i < 3; i++) { + if (tagHeaderBuffer[i] != IDV3_TAG[i]) { + frameReader.appendToScanBuffer(tagHeaderBuffer, 0, 3); + System.arraycopy(lastTagHeader, 0, tagHeaderBuffer, 0, 4); + return; + } } - } - int majorVersion = dataInput.readByte() & 0xFF; - // Minor version - dataInput.readByte(); + int majorVersion = dataInput.readByte() & 0xFF; + // Minor version + dataInput.readByte(); - if (majorVersion < 2 || majorVersion > 5) { - return; - } + if (majorVersion < 2 || majorVersion > 5) { + return; + } - int flags = dataInput.readByte() & 0xFF; - int tagsSize = readSyncProofInteger(); + int flags = dataInput.readByte() & 0xFF; + int tagsSize = readSyncProofInteger(); - long tagsEndPosition = inputStream.getPosition() + tagsSize; + long tagsEndPosition = inputStream.getPosition() + tagsSize; - skipExtendedHeader(flags); + skipExtendedHeader(flags); - if (majorVersion < 5) { - parseIdv3Frames(majorVersion, tagsEndPosition); - } + if (majorVersion < 5) { + parseIdv3Frames(majorVersion, tagsEndPosition); + } - inputStream.seek(tagsEndPosition); + inputStream.seek(tagsEndPosition); + } } private int readSyncProofInteger() throws IOException {