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_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' };
|
||||||
|
|
|
@ -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 {
|
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.
Loading…
Reference in New Issue