aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/github/daneren2005/dsub/util/tags/OpusFile.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/github/daneren2005/dsub/util/tags/OpusFile.java')
-rw-r--r--app/src/main/java/github/daneren2005/dsub/util/tags/OpusFile.java127
1 files changed, 127 insertions, 0 deletions
diff --git a/app/src/main/java/github/daneren2005/dsub/util/tags/OpusFile.java b/app/src/main/java/github/daneren2005/dsub/util/tags/OpusFile.java
new file mode 100644
index 00000000..38b402bb
--- /dev/null
+++ b/app/src/main/java/github/daneren2005/dsub/util/tags/OpusFile.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2015 Adrian Ulrich <adrian@blinkenlights.ch>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+package github.daneren2005.dsub.util.tags;
+
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.HashMap;
+
+
+public class OpusFile extends OggFile {
+ // A list of tags we are going to ignore in the OpusTags section
+ public static final String[] FORBIDDEN_TAGS = {"REPLAYGAIN_TRACK_GAIN", "REPLAYGAIN_TRACK_PEAK", "REPLAYGAIN_ALBUM_GAIN", "REPLAYGAIN_ALBUM_PEAK"};
+
+ public OpusFile() {
+ }
+
+ public HashMap getTags(RandomAccessFile s) throws IOException {
+
+ // The opus specification is very strict: The first packet MUST
+ // contain the OpusHeader while the 2nd MUST contain the
+ // OggHeader payload: https://wiki.xiph.org/OggOpus
+ long pos = 0;
+ PageInfo pi = parse_stream_page(s, pos);
+
+ HashMap tags = new HashMap();
+ HashMap opus_head = parse_opus_head(s, pos+pi.header_len, pi.payload_len);
+ pos += pi.header_len+pi.payload_len;
+
+ // Check if we parsed a version number and ensure it doesn't have any
+ // of the upper 4 bits set (eg: <= 15)
+ if(opus_head.containsKey("version") && (Integer)opus_head.get("version") <= 0xF) {
+ // Get next page: The spec requires this to be an OpusTags head
+ pi = parse_stream_page(s, pos);
+ tags = parse_opus_vorbis_comment(s, pos+pi.header_len, pi.payload_len);
+ // ...and merge replay gain intos into the tags map
+ calculate_gain(opus_head, tags);
+ }
+
+ return tags;
+ }
+
+ /**
+ * Adds replay gain information to the tags hash map
+ */
+ private void calculate_gain(HashMap header, HashMap tags) {
+ // Remove any unacceptable tags (Opus files must not have
+ // their own REPLAYGAIN_* fields)
+ for(String k : FORBIDDEN_TAGS) {
+ tags.remove(k);
+ }
+ // Include the gain value found in the opus header
+ int header_gain = (Integer)header.get("header_gain");
+ addTagEntry(tags, "R128_BASTP_BASE_GAIN", ""+header_gain);
+ }
+
+
+ /**
+ * Attempts to parse an OpusHead block at given offset.
+ * Returns an hash-map, will be empty on failure
+ */
+ private HashMap parse_opus_head(RandomAccessFile s, long offset, long pl_len) throws IOException {
+ /* Structure:
+ * 8 bytes of 'OpusHead'
+ * 1 byte version
+ * 1 byte channel count
+ * 2 bytes pre skip
+ * 4 bytes input-sample-rate
+ * 2 bytes outputGain as Q7.8
+ * 1 byte channel map
+ * --> 19 bytes
+ */
+
+ HashMap id_hash = new HashMap();
+ byte[] buff = new byte[19];
+ if(pl_len >= buff.length) {
+ s.seek(offset);
+ s.read(buff);
+ if((new String(buff, 0, 8)).equals("OpusHead")) {
+ id_hash.put("version" , b2u(buff[8]));
+ id_hash.put("channels" , b2u(buff[9]));
+ id_hash.put("pre_skip" , b2le16(buff, 10));
+ id_hash.put("sampling_rate", b2le32(buff, 12));
+ id_hash.put("header_gain" , (int)((short)b2le16(buff, 16)));
+ id_hash.put("channel_map" , b2u(buff[18]));
+ }
+ }
+
+ return id_hash;
+ }
+
+ /**
+ * Parses an OpusTags section
+ * Returns a hash map of the found tags
+ */
+ private HashMap parse_opus_vorbis_comment(RandomAccessFile s, long offset, long pl_len) throws IOException {
+ final int magic_len = 8; // OpusTags
+ byte[] magic = new byte[magic_len];
+
+ if(pl_len < magic_len)
+ xdie("opus comment field is too short!");
+
+ // Read and check magic signature
+ s.seek(offset);
+ s.read(magic);
+
+ if((new String(magic, 0, magic_len)).equals("OpusTags") == false)
+ xdie("Damaged packet found!");
+
+ return parse_vorbis_comment(s, this, offset+magic_len, pl_len-magic_len);
+ }
+
+}