Support parsing vorbis comments spanning across pages (#7215)
This commit is contained in:
parent
e2ff09bd34
commit
91f8ed055f
|
@ -20,16 +20,14 @@ public abstract class VorbisCommentReader {
|
|||
private static final int PACKET_TYPE_IDENTIFICATION = 1;
|
||||
private static final int PACKET_TYPE_COMMENT = 3;
|
||||
|
||||
private final InputStream input;
|
||||
private final VorbisInputStream input;
|
||||
|
||||
VorbisCommentReader(InputStream input) {
|
||||
this.input = input;
|
||||
this.input = new VorbisInputStream(input);
|
||||
}
|
||||
|
||||
public void readInputStream() throws VorbisCommentReaderException {
|
||||
try {
|
||||
findIdentificationHeader();
|
||||
findOggPage();
|
||||
findCommentHeader();
|
||||
VorbisCommentHeader commentHeader = readCommentHeader();
|
||||
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 {
|
||||
try {
|
||||
long vectorLength = EndianUtils.readSwappedUnsignedInteger(input);
|
||||
|
@ -90,25 +68,6 @@ public abstract class VorbisCommentReader {
|
|||
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 {
|
||||
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' };
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ public class VorbisCommentMetadataReaderTest {
|
|||
public void testRealFilesAuphonic() throws IOException, VorbisCommentReaderException {
|
||||
testRealFileAuphonic("auphonic.ogg");
|
||||
testRealFileAuphonic("auphonic.opus");
|
||||
testRealFileAuphonic("opus-comment.opus");
|
||||
}
|
||||
|
||||
public void testRealFileAuphonic(String filename) throws IOException, VorbisCommentReaderException {
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue