Support parsing vorbis comments spanning across pages (#7215)

This commit is contained in:
Senventise 2024-06-09 16:04:59 +08:00 committed by GitHub
parent e2ff09bd34
commit 91f8ed055f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 72 additions and 43 deletions

View File

@ -20,16 +20,14 @@ public abstract class VorbisCommentReader {
private static final int PACKET_TYPE_IDENTIFICATION = 1; private static final int PACKET_TYPE_IDENTIFICATION = 1;
private static final int PACKET_TYPE_COMMENT = 3; private static final int PACKET_TYPE_COMMENT = 3;
private final InputStream input; private final VorbisInputStream input;
VorbisCommentReader(InputStream input) { VorbisCommentReader(InputStream input) {
this.input = input; this.input = new VorbisInputStream(input);
} }
public void readInputStream() throws VorbisCommentReaderException { public void readInputStream() throws VorbisCommentReaderException {
try { try {
findIdentificationHeader();
findOggPage();
findCommentHeader(); findCommentHeader();
VorbisCommentHeader commentHeader = readCommentHeader(); VorbisCommentHeader commentHeader = readCommentHeader();
Log.d(TAG, commentHeader.toString()); Log.d(TAG, commentHeader.toString());
@ -41,26 +39,6 @@ public abstract class VorbisCommentReader {
} }
} }
private void findOggPage() throws IOException {
// find OggS
byte[] buffer = new byte[4];
final byte[] oggPageHeader = {'O', 'g', 'g', 'S'};
for (int bytesRead = 0; bytesRead < SECOND_PAGE_MAX_LENGTH; bytesRead++) {
int data = input.read();
if (data == -1) {
throw new IOException("EOF while trying to find vorbis page");
}
buffer[bytesRead % buffer.length] = (byte) data;
if (bufferMatches(buffer, oggPageHeader, bytesRead)) {
break;
}
}
// read segments
IOUtils.skipFully(input, 22);
int numSegments = input.read();
IOUtils.skipFully(input, numSegments);
}
private void readUserComment() throws VorbisCommentReaderException { private void readUserComment() throws VorbisCommentReaderException {
try { try {
long vectorLength = EndianUtils.readSwappedUnsignedInteger(input); long vectorLength = EndianUtils.readSwappedUnsignedInteger(input);
@ -90,25 +68,6 @@ public abstract class VorbisCommentReader {
return charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString(); return charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString();
} }
/**
* Looks for an identification header in the first page of the file. If an
* identification header is found, it will be skipped completely
*/
private void findIdentificationHeader() throws IOException {
byte[] buffer = new byte[FIRST_OPUS_PAGE_LENGTH];
IOUtils.readFully(input, buffer);
final byte[] oggIdentificationHeader = new byte[]{ PACKET_TYPE_IDENTIFICATION, 'v', 'o', 'r', 'b', 'i', 's' };
for (int i = 6; i < buffer.length; i++) {
if (bufferMatches(buffer, oggIdentificationHeader, i)) {
IOUtils.skip(input, FIRST_OGG_PAGE_LENGTH - FIRST_OPUS_PAGE_LENGTH);
return;
} else if (bufferMatches(buffer, "OpusHead".getBytes(), i)) {
return;
}
}
throw new IOException("No vorbis identification header found");
}
private void findCommentHeader() throws IOException { private void findCommentHeader() throws IOException {
byte[] buffer = new byte[64]; // Enough space for some bytes. Used circularly. byte[] buffer = new byte[64]; // Enough space for some bytes. Used circularly.
final byte[] oggCommentHeader = new byte[]{ PACKET_TYPE_COMMENT, 'v', 'o', 'r', 'b', 'i', 's' }; final byte[] oggCommentHeader = new byte[]{ PACKET_TYPE_COMMENT, 'v', 'o', 'r', 'b', 'i', 's' };

View File

@ -0,0 +1,69 @@
package de.danoeh.antennapod.parser.media.vorbis;
import org.apache.commons.io.IOUtils;
import java.io.BufferedInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
public class VorbisInputStream extends FilterInputStream {
private static final byte[] CAPTURE_PATTERN = {'O', 'g', 'g', 'S'};
private static final int HEADER_SKIP_LENGTH = 1 + 1 + 8 + 4 + 4 + 4;
private final BufferedInputStream inputStream;
private int pageRemainBytes = 0;
protected VorbisInputStream(InputStream in) {
super(in);
inputStream = new BufferedInputStream(in);
}
private int parsePageHeader(InputStream in) throws IOException {
byte[] capturePattern = new byte[4];
IOUtils.readFully(in, capturePattern, 0, 4);
if (!Arrays.equals(CAPTURE_PATTERN, capturePattern)) {
throw new IOException("Invalid page header");
}
IOUtils.skipFully(in, HEADER_SKIP_LENGTH);
int pageSegments = in.read();
byte[] segmentTable = new byte[pageSegments];
int pageLength = 0;
IOUtils.readFully(in, segmentTable);
for (byte segment:segmentTable) {
pageLength += (segment & 0xff);
}
return pageLength;
}
/** check and update remaining bytes **/
private void updateRemainBytes() throws IOException {
if (pageRemainBytes == 0) {
pageRemainBytes = parsePageHeader(inputStream);
} else if (pageRemainBytes < 0) {
throw new IOException("Page remain bytes less than 0");
}
}
@Override
public int read() throws IOException {
updateRemainBytes();
pageRemainBytes--;
return inputStream.read();
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
updateRemainBytes();
int bytesToRead = Math.min(len, pageRemainBytes);
IOUtils.readFully(inputStream, b, off, bytesToRead);
this.pageRemainBytes -= bytesToRead;
return bytesToRead;
}
}

View File

@ -16,6 +16,7 @@ public class VorbisCommentMetadataReaderTest {
public void testRealFilesAuphonic() throws IOException, VorbisCommentReaderException { public void testRealFilesAuphonic() throws IOException, VorbisCommentReaderException {
testRealFileAuphonic("auphonic.ogg"); testRealFileAuphonic("auphonic.ogg");
testRealFileAuphonic("auphonic.opus"); testRealFileAuphonic("auphonic.opus");
testRealFileAuphonic("opus-comment.opus");
} }
public void testRealFileAuphonic(String filename) throws IOException, VorbisCommentReaderException { public void testRealFileAuphonic(String filename) throws IOException, VorbisCommentReaderException {

Binary file not shown.