Skip to content

Commit

Permalink
Fix handling of multiple MP3 IDv3 blocks, add basic Matroska metadata…
Browse files Browse the repository at this point in the history
… extraction (#108)

* Add basic Matroska metadata extraction.

* Read all IDv3 blocks in an MP3 file

* inline while condition
  • Loading branch information
devoxin committed May 5, 2024
1 parent 0ab6311 commit 52ada5c
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MatroskaFileTrack> trackList = new ArrayList<>();
Expand All @@ -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
*/
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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}),
Expand Down Expand Up @@ -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<Long, MatroskaElementType> mapping;
private static final Map<Long, MatroskaElementType> mapping;

/**
* The ID as EBML code bytes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit 52ada5c

Please sign in to comment.