sync BASTP with upstream b2ace816e9f53702f4cf4e199582430705664bb7
This commit is contained in:
parent
a4a5d477b6
commit
f3494c1877
@ -56,9 +56,15 @@ public class Bastp {
|
|||||||
tags.put("type", "FLAC");
|
tags.put("type", "FLAC");
|
||||||
}
|
}
|
||||||
else if(magic.equals("OggS")) {
|
else if(magic.equals("OggS")) {
|
||||||
|
// This may be an Opus OR an Ogg Vorbis file
|
||||||
|
tags = (new OpusFile()).getTags(s);
|
||||||
|
if (tags.size() > 0) {
|
||||||
|
tags.put("type", "OPUS");
|
||||||
|
} else {
|
||||||
tags = (new OggFile()).getTags(s);
|
tags = (new OggFile()).getTags(s);
|
||||||
tags.put("type", "OGG");
|
tags.put("type", "OGG");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else if(file_ff[0] == -1 && file_ff[1] == -5) { /* aka 0xfffb in real languages */
|
else if(file_ff[0] == -1 && file_ff[1] == -5) { /* aka 0xfffb in real languages */
|
||||||
tags = (new LameHeader()).getTags(s);
|
tags = (new LameHeader()).getTags(s);
|
||||||
tags.put("type", "MP3/Lame");
|
tags.put("type", "MP3/Lame");
|
||||||
|
@ -49,6 +49,13 @@ public class Common {
|
|||||||
return((i&0xff)<<24)+((i&0xff00)<<8)+((i&0xff0000)>>8)+((i>>24)&0xff);
|
return((i&0xff)<<24)+((i&0xff00)<<8)+((i&0xff0000)>>8)+((i>>24)&0xff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
** Returns a 16bit int from given byte offset in LE
|
||||||
|
*/
|
||||||
|
public int b2le16(byte[] b, int off) {
|
||||||
|
return ( b2u(b[off]) | b2u(b[off+1]) << 8 );
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** convert 'byte' value into unsigned int
|
** convert 'byte' value into unsigned int
|
||||||
*/
|
*/
|
||||||
@ -73,13 +80,11 @@ public class Common {
|
|||||||
// seek to given position and slurp in the payload
|
// seek to given position and slurp in the payload
|
||||||
s.seek(offset);
|
s.seek(offset);
|
||||||
s.read(scratch);
|
s.read(scratch);
|
||||||
|
|
||||||
// skip vendor string in format: [LEN][VENDOR_STRING]
|
// skip vendor string in format: [LEN][VENDOR_STRING]
|
||||||
xoff += 4 + b2le32(scratch, xoff); // 4 = LEN = 32bit int
|
xoff += 4 + b2le32(scratch, xoff); // 4 = LEN = 32bit int
|
||||||
comments = b2le32(scratch, xoff);
|
comments = b2le32(scratch, xoff);
|
||||||
xoff += 4;
|
xoff += 4;
|
||||||
|
|
||||||
// debug("comments count = "+comments);
|
|
||||||
for(int i=0; i<comments; i++) {
|
for(int i=0; i<comments; i++) {
|
||||||
|
|
||||||
int clen = (int)b2le32(scratch, xoff);
|
int clen = (int)b2le32(scratch, xoff);
|
||||||
|
@ -74,7 +74,7 @@ public class OggFile extends Common {
|
|||||||
/* Parses the ogg page at offset 'offset' and returns
|
/* Parses the ogg page at offset 'offset' and returns
|
||||||
** [header_size, payload_size, type]
|
** [header_size, payload_size, type]
|
||||||
*/
|
*/
|
||||||
private long[] parse_ogg_page(RandomAccessFile s, long offset) throws IOException {
|
protected long[] parse_ogg_page(RandomAccessFile s, long offset) throws IOException {
|
||||||
long[] result = new long[3]; // [header_size, payload_size]
|
long[] result = new long[3]; // [header_size, payload_size]
|
||||||
byte[] p_header = new byte[OGG_PAGE_SIZE]; // buffer for the page header
|
byte[] p_header = new byte[OGG_PAGE_SIZE]; // buffer for the page header
|
||||||
byte[] scratch;
|
byte[] scratch;
|
||||||
@ -155,7 +155,7 @@ public class OggFile extends Common {
|
|||||||
s.read(buff);
|
s.read(buff);
|
||||||
id_hash.put("version" , b2le32(buff, 7));
|
id_hash.put("version" , b2le32(buff, 7));
|
||||||
id_hash.put("channels" , b2u(buff[11]));
|
id_hash.put("channels" , b2u(buff[11]));
|
||||||
id_hash.put("samplint_rate" , b2le32(buff, 12));
|
id_hash.put("sampling_rate" , b2le32(buff, 12));
|
||||||
id_hash.put("bitrate_minimal" , b2le32(buff, 16));
|
id_hash.put("bitrate_minimal" , b2le32(buff, 16));
|
||||||
id_hash.put("bitrate_nominal" , b2le32(buff, 20));
|
id_hash.put("bitrate_nominal" , b2le32(buff, 20));
|
||||||
id_hash.put("bitrate_maximal" , b2le32(buff, 24));
|
id_hash.put("bitrate_maximal" , b2le32(buff, 24));
|
||||||
|
127
src/ch/blinkenlights/bastp/OpusFile.java
Normal file
127
src/ch/blinkenlights/bastp/OpusFile.java
Normal file
@ -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 ch.blinkenlights.bastp;
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
long offsets[] = parse_ogg_page(s, pos);
|
||||||
|
|
||||||
|
HashMap tags = new HashMap();
|
||||||
|
HashMap opus_head = parse_opus_head(s, pos+offsets[0], offsets[1]);
|
||||||
|
pos += offsets[0]+offsets[1];
|
||||||
|
|
||||||
|
// 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
|
||||||
|
offsets = parse_ogg_page(s, pos);
|
||||||
|
tags = parse_opus_vorbis_comment(s, pos+offsets[0], offsets[1]);
|
||||||
|
// ...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, offset+magic_len, pl_len-magic_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user