From 39cc4b868a65cbe5005655b2223571825e09b917 Mon Sep 17 00:00:00 2001 From: =?utf8?q?David=20=E2=80=98Bombe=E2=80=99=20Roden?= Date: Mon, 3 Jun 2013 22:22:37 +0200 Subject: [PATCH] Add parser for FLAC metadata. --- .../pterodactylus/sonitus/io/flac/BlockType.java | 96 +++++++++++++++++ .../net/pterodactylus/sonitus/io/flac/Data.java | 114 +++++++++++++++++++++ .../net/pterodactylus/sonitus/io/flac/Header.java | 114 +++++++++++++++++++++ .../sonitus/io/flac/MetadataBlock.java | 91 ++++++++++++++++ .../net/pterodactylus/sonitus/io/flac/Stream.java | 107 +++++++++++++++++++ .../pterodactylus/sonitus/io/flac/StreamInfo.java | 113 ++++++++++++++++++++ 6 files changed, 635 insertions(+) create mode 100644 src/main/java/net/pterodactylus/sonitus/io/flac/BlockType.java create mode 100644 src/main/java/net/pterodactylus/sonitus/io/flac/Data.java create mode 100644 src/main/java/net/pterodactylus/sonitus/io/flac/Header.java create mode 100644 src/main/java/net/pterodactylus/sonitus/io/flac/MetadataBlock.java create mode 100644 src/main/java/net/pterodactylus/sonitus/io/flac/Stream.java create mode 100644 src/main/java/net/pterodactylus/sonitus/io/flac/StreamInfo.java diff --git a/src/main/java/net/pterodactylus/sonitus/io/flac/BlockType.java b/src/main/java/net/pterodactylus/sonitus/io/flac/BlockType.java new file mode 100644 index 0000000..8cecc02 --- /dev/null +++ b/src/main/java/net/pterodactylus/sonitus/io/flac/BlockType.java @@ -0,0 +1,96 @@ +/* + * Sonitus - BlockType.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sonitus.io.flac; + +/** + * The type of a metadata block. + * + * @author David ‘Bombe’ Roden + */ +public enum BlockType { + + /** A STREAMINFO block. */ + STREAMINFO { + @Override + public Data createData(byte[] content) { + return new StreamInfo(content); + } + }, + + /** A PADDING block. */ + PADDING, + + /** An APPLICATION block. */ + APPLICATION, + + /** A SEEKTABLE block. */ + SEEKTABLE, + + /** A VORBIS_COMMENT block. */ + VORBIS_COMMENT, + + /** A CUESHEET block. */ + CUESHEET, + + /** A PICTURE block. */ + PICTURE, + + /** A RESERVED block. */ + RESERVED, + + /** An INVALID block. */ + INVALID; + + // + // ACTIONS + // + + /** + * Creates a {@link Data} object from the given byte array. Block type + * enumeration values can override this to return specialized parser objects. + * + * @param content + * The content of the metadata block + * @return The metadata block as a data object + */ + public Data createData(byte[] content) { + return new Data(content); + } + + // + // STATIC METHODS + // + + /** + * Creates a block type from the given block type number. + * + * @param blockType + * The block type number + * @return The parsed block type + */ + public static BlockType valueOf(int blockType) { + if ((blockType >= 0) && (blockType <= 6)) { + return values()[blockType]; + } + if ((blockType > 6) && (blockType < 127)) { + return RESERVED; + } + return INVALID; + } + +} diff --git a/src/main/java/net/pterodactylus/sonitus/io/flac/Data.java b/src/main/java/net/pterodactylus/sonitus/io/flac/Data.java new file mode 100644 index 0000000..d054494 --- /dev/null +++ b/src/main/java/net/pterodactylus/sonitus/io/flac/Data.java @@ -0,0 +1,114 @@ +/* + * Sonitus - Data.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sonitus.io.flac; + +import static com.google.common.io.ByteStreams.readFully; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Accessor type that can parse the contents of a {@link MetadataBlock}. + * + * @author David ‘Bombe’ Roden + */ +public class Data { + + /** The content of the metadata block. */ + private final byte[] content; + + /** + * Creates a new data accessor. + * + * @param content + * The content of the metadata block + */ + public Data(byte[] content) { + this.content = content; + } + + // + // ACCESSORS + // + + /** + * Returns the content of this metadata block. + * + * @return The content of the metadata block + */ + public byte[] content() { + return content; + } + + // + // SUBCLASS METHODS + // + + /** + * Parses the given number of bits from the content of this metadata block, + * starting at the given byte offset and bit offset (bit 0 being the most + * significant bit). + * + * @param byteOffset + * The byte offset at which to start reading + * @param bitOffset + * The bit offset at which to start reading + * @param numberOfBits + * The number of bits to parse (should be <= 64) + * @return The parsed bits + */ + protected long parseBits(int byteOffset, int bitOffset, int numberOfBits) { + long value = 0; + int currentByteOffset = byteOffset; + int currentBitOffset = bitOffset; + int bitsRemaining = numberOfBits; + + while (bitsRemaining > 0) { + value = (value << Math.min(8, bitsRemaining)) | ((content[currentByteOffset] & (0xff >>> currentBitOffset)) >> (8 - currentBitOffset - Math.min(bitsRemaining, 8 - currentBitOffset))); + bitsRemaining -= Math.min(bitsRemaining, 8 - currentBitOffset); + currentBitOffset = 0; + currentByteOffset++; + } + + return value; + } + + // + // STATIC METHODS + // + + /** + * Creates a new data accessor from the given input stream. + * + * @param inputStream + * The input stream to read the contents of the metadata block from + * @param blockType + * The type of the metadata block + * @param length + * The length of the metadata block + * @return The parsed metadata block + * @throws IOException + * if an I/O error occurs + */ + public static Data parse(InputStream inputStream, BlockType blockType, int length) throws IOException { + byte[] buffer = new byte[length]; + readFully(inputStream, buffer); + return blockType.createData(buffer); + } + +} diff --git a/src/main/java/net/pterodactylus/sonitus/io/flac/Header.java b/src/main/java/net/pterodactylus/sonitus/io/flac/Header.java new file mode 100644 index 0000000..bd6770e --- /dev/null +++ b/src/main/java/net/pterodactylus/sonitus/io/flac/Header.java @@ -0,0 +1,114 @@ +/* + * Sonitus - Header.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sonitus.io.flac; + +import static com.google.common.io.ByteStreams.readFully; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Header for a {@link MetadataBlock}. + * + * @author David ‘Bombe’ Roden + */ +public class Header { + + /** Whether this metadata block is the last metadata block. */ + private final boolean lastMetadataBlock; + + /** The type of the metadata block. */ + private final BlockType blockType; + + /** The length of the metadata block. */ + private final int length; + + /** + * Creates a new metadata block header. + * + * @param lastMetadataBlock + * {@code true} if this metadata block is the last metadata block in the FLAC + * stream, {@code false} otherwise + * @param blockType + * The type of the metadata block + * @param length + * The length of the metadata block + */ + private Header(boolean lastMetadataBlock, BlockType blockType, int length) { + this.lastMetadataBlock = lastMetadataBlock; + this.blockType = blockType; + this.length = length; + } + + // + // ACCESSORS + // + + /** + * Returns whether this metadata block is the last metadata block in the FLAC + * {@link Stream}. + * + * @return {@code true} if this metadata block is last metadata block in the + * FLAC stream, {@code false} otherwise + */ + public boolean isLastMetadataBlock() { + return lastMetadataBlock; + } + + /** + * Returns the type of the metadata block. + * + * @return The type of the metadata block + */ + public BlockType blockType() { + return blockType; + } + + /** + * Returns the length of the metadata block. + * + * @return The length of the metadata block + */ + public int length() { + return length; + } + + // + // STATIC METHODS + // + + /** + * Parses a metadata block header from the current position of the given input + * stream. + * + * @param inputStream + * The input stream to parse the header from + * @return The parsed header + * @throws IOException + * if an I/O error occurs + */ + public static Header parse(InputStream inputStream) throws IOException { + byte[] buffer = new byte[4]; + readFully(inputStream, buffer); + boolean lastMetadataBlock = ((buffer[0] >> 7) & 0x01) != 0; + BlockType blockType = BlockType.valueOf(buffer[0] & 0x7f); + int length = ((buffer[1] & 0xff) << 16) | ((buffer[2] & 0xff) << 8) | (buffer[3] & 0xff); + return new Header(lastMetadataBlock, blockType, length); + } + +} diff --git a/src/main/java/net/pterodactylus/sonitus/io/flac/MetadataBlock.java b/src/main/java/net/pterodactylus/sonitus/io/flac/MetadataBlock.java new file mode 100644 index 0000000..9966714 --- /dev/null +++ b/src/main/java/net/pterodactylus/sonitus/io/flac/MetadataBlock.java @@ -0,0 +1,91 @@ +/* + * Sonitus - MetadataBlock.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sonitus.io.flac; + +import java.io.IOException; +import java.io.InputStream; + +/** + * A metadata block. + * + * @author David ‘Bombe’ Roden + */ +public class MetadataBlock { + + /** The header of the metadata block. */ + private final Header header; + + /** The data of the metadata block. */ + private final Data data; + + /** + * Creates a new metadata block. + * + * @param header + * The header of the metadata block + * @param data + * The data of the metadata block + */ + MetadataBlock(Header header, Data data) { + this.header = header; + this.data = data; + } + + // + // ACCESSORS + // + + /** + * Returns the header of this metadata block. + * + * @return The header of this metadata block + */ + public Header header() { + return header; + } + + /** + * Returns the data of this metadata block. + * + * @return The data of this metadata block + */ + public Data data() { + return data; + } + + // + // STATIC METHODS + // + + /** + * Parses the metadata block from the current position of the given input + * stream. + * + * @param inputStream + * The input stream to parse the metadata block from + * @return The parsed metadata block + * @throws IOException + * if an I/O error occurs + */ + public static MetadataBlock parse(InputStream inputStream) throws IOException { + Header header = Header.parse(inputStream); + Data data = Data.parse(inputStream, header.blockType(), header.length()); + return new MetadataBlock(header, data); + } + +} diff --git a/src/main/java/net/pterodactylus/sonitus/io/flac/Stream.java b/src/main/java/net/pterodactylus/sonitus/io/flac/Stream.java new file mode 100644 index 0000000..3b4ee59 --- /dev/null +++ b/src/main/java/net/pterodactylus/sonitus/io/flac/Stream.java @@ -0,0 +1,107 @@ +/* + * Sonitus - MetadataBlock.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sonitus.io.flac; + +import static com.google.common.io.ByteStreams.readFully; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Lists; + +/** + * Parser and container for information about a FLAC stream. + * + * @author David ‘Bombe’ Roden + */ +public class Stream { + + /** The metadata blocks of the stream. */ + private final List metadataBlocks = Lists.newArrayList(); + + /** + * Creates a new FLAC stream containing the given metadata blocks. + * + * @param metadataBlocks + * The metadata blocks in order of appearance + */ + private Stream(List metadataBlocks) { + this.metadataBlocks.addAll(metadataBlocks); + } + + // + // ACCESSORS + // + + /** + * Returns all metadata blocks of the given block type, in the order they + * appear in this stream. + * + * @param blockType + * The block type to get all metadata blocks for + * @return The metadata blocks of the given block type + */ + public Collection metadataBlocks(final BlockType blockType) { + return FluentIterable.from(metadataBlocks).filter(new Predicate() { + + @Override + public boolean apply(MetadataBlock metadataBlock) { + return metadataBlock.header().blockType() == blockType; + } + }).toList(); + } + + // + // STATIC METHODS + // + + /** + * Parses the given input stream and returns information about the stream if it + * can be successfully parsed as a FLAC stream. + * + * @param inputStream + * The input stream containing the FLAC stream + * @return The parsed FLAC stream, or {@link Optional#absent()} if no FLAC + * stream could be found at the stream’s current position + * @throws IOException + * if an I/O error occurs + */ + public static Optional parse(InputStream inputStream) throws IOException { + byte[] streamTag = new byte[4]; + readFully(inputStream, streamTag); + if (!Arrays.equals(streamTag, new byte[] { 'f', 'L', 'a', 'C' })) { + return Optional.absent(); + } + List metadataBlocks = Lists.newArrayList(); + while (true) { + MetadataBlock metadataBlock = MetadataBlock.parse(inputStream); + metadataBlocks.add(metadataBlock); + if (metadataBlock.header().isLastMetadataBlock()) { + break; + } + } + return Optional.of(new Stream(metadataBlocks)); + } + +} diff --git a/src/main/java/net/pterodactylus/sonitus/io/flac/StreamInfo.java b/src/main/java/net/pterodactylus/sonitus/io/flac/StreamInfo.java new file mode 100644 index 0000000..5dadd65 --- /dev/null +++ b/src/main/java/net/pterodactylus/sonitus/io/flac/StreamInfo.java @@ -0,0 +1,113 @@ +/* + * Sonitus - StreamInfo.java - Copyright © 2013 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sonitus.io.flac; + +/** + * Parser for a {@link BlockType#STREAMINFO} metadata block. + * + * @author David ‘Bombe’ Roden + */ +public class StreamInfo extends Data { + + /** + * Creates a new STREAMINFO block from the given buffer. + * + * @param content + * The contents of the metadata block + */ + public StreamInfo(byte[] content) { + super(content); + } + + // + // ACCESSORS + // + + /** + * Returns the minimum block size. + * + * @return The minimum block size (in samples) + */ + public int minimumBlockSize() { + return (int) parseBits(0, 0, 16); + } + + /** + * Returns the maximum block size. + * + * @return The maximum block size (in samples) + */ + public int maximumBlockSize() { + return (int) parseBits(2, 0, 16); + } + + /** + * Returns the minimum frame size. + * + * @return The minimum frame size (in bytes) + */ + public int minimumFrameSize() { + return (int) parseBits(4, 0, 24); + } + + /** + * Returns the maximum frame size. + * + * @return The maximum frame size (in bytes) + */ + public int maximumFrameSize() { + return (int) parseBits(7, 0, 24); + } + + /** + * Returns the sample rate. + * + * @return The sample rate (in Hertz) + */ + public int sampleRate() { + return (int) parseBits(10, 0, 20); + } + + /** + * Returns the number of channels. + * + * @return The number of channels + */ + public int numberOfChannels() { + return (int) (parseBits(12, 4, 3) + 1); + } + + /** + * Returns the number of bits per sample. + * + * @return The number of bits per sample + */ + public int bitsPerSample() { + return (int) (parseBits(12, 7, 5) + 1); + } + + /** + * Returns the total number of samples. + * + * @return The total number of samples + */ + public long totalSamples() { + return parseBits(13, 4, 36); + } + +} -- 2.7.4