Big cleanup of mp4 metadata extraction
This commit is contained in:
parent
1b39d21ed4
commit
8caaf0b5d9
@ -154,6 +154,18 @@ import java.util.Locale;
|
||||
}
|
||||
Log.d(TAG, " ]");
|
||||
}
|
||||
// Log metadata for at most one of the tracks selected for the renderer.
|
||||
if (trackSelection != null) {
|
||||
for (int selectionIndex = 0; selectionIndex < trackSelection.length(); selectionIndex++) {
|
||||
Metadata metadata = trackSelection.getFormat(selectionIndex).metadata;
|
||||
if (metadata != null) {
|
||||
Log.d(TAG, " Metadata [");
|
||||
printMetadata(metadata, " ");
|
||||
Log.d(TAG, " ]");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Log.d(TAG, " ]");
|
||||
}
|
||||
}
|
||||
@ -184,7 +196,7 @@ import java.util.Locale;
|
||||
@Override
|
||||
public void onMetadata(Metadata metadata) {
|
||||
Log.d(TAG, "onMetadata [");
|
||||
printMetadata(metadata);
|
||||
printMetadata(metadata, " ");
|
||||
Log.d(TAG, "]");
|
||||
}
|
||||
|
||||
@ -208,13 +220,8 @@ import java.util.Locale;
|
||||
|
||||
@Override
|
||||
public void onAudioInputFormatChanged(Format format) {
|
||||
boolean hasMetadata = format.metadata != null;
|
||||
Log.d(TAG, "audioFormatChanged [" + getSessionTimeString() + ", " + getFormatString(format)
|
||||
+ (hasMetadata ? "" : "]"));
|
||||
if (hasMetadata) {
|
||||
printMetadata(format.metadata);
|
||||
Log.d(TAG, "]");
|
||||
}
|
||||
+ "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -335,35 +342,35 @@ import java.util.Locale;
|
||||
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
|
||||
}
|
||||
|
||||
private void printMetadata(Metadata metadata) {
|
||||
private void printMetadata(Metadata metadata, String prefix) {
|
||||
for (int i = 0; i < metadata.length(); i++) {
|
||||
Metadata.Entry entry = metadata.get(i);
|
||||
if (entry instanceof TxxxFrame) {
|
||||
TxxxFrame txxxFrame = (TxxxFrame) entry;
|
||||
Log.d(TAG, String.format(" %s: description=%s, value=%s", txxxFrame.id,
|
||||
Log.d(TAG, prefix + String.format("%s: description=%s, value=%s", txxxFrame.id,
|
||||
txxxFrame.description, txxxFrame.value));
|
||||
} else if (entry instanceof PrivFrame) {
|
||||
PrivFrame privFrame = (PrivFrame) entry;
|
||||
Log.d(TAG, String.format(" %s: owner=%s", privFrame.id, privFrame.owner));
|
||||
Log.d(TAG, prefix + String.format("%s: owner=%s", privFrame.id, privFrame.owner));
|
||||
} else if (entry instanceof GeobFrame) {
|
||||
GeobFrame geobFrame = (GeobFrame) entry;
|
||||
Log.d(TAG, String.format(" %s: mimeType=%s, filename=%s, description=%s",
|
||||
Log.d(TAG, prefix + String.format("%s: mimeType=%s, filename=%s, description=%s",
|
||||
geobFrame.id, geobFrame.mimeType, geobFrame.filename, geobFrame.description));
|
||||
} else if (entry instanceof ApicFrame) {
|
||||
ApicFrame apicFrame = (ApicFrame) entry;
|
||||
Log.d(TAG, String.format(" %s: mimeType=%s, description=%s",
|
||||
Log.d(TAG, prefix + String.format("%s: mimeType=%s, description=%s",
|
||||
apicFrame.id, apicFrame.mimeType, apicFrame.description));
|
||||
} else if (entry instanceof TextInformationFrame) {
|
||||
TextInformationFrame textInformationFrame = (TextInformationFrame) entry;
|
||||
Log.d(TAG, String.format(" %s: description=%s", textInformationFrame.id,
|
||||
Log.d(TAG, prefix + String.format("%s: description=%s", textInformationFrame.id,
|
||||
textInformationFrame.description));
|
||||
} else if (entry instanceof CommentFrame) {
|
||||
CommentFrame commentFrame = (CommentFrame) entry;
|
||||
Log.d(TAG, String.format(" %s: language=%s description=%s", commentFrame.id,
|
||||
commentFrame.language, commentFrame.description));
|
||||
Log.d(TAG, prefix + String.format("%s: language=%s description=%s", commentFrame.id,
|
||||
commentFrame.language, commentFrame.description, commentFrame.text));
|
||||
} else if (entry instanceof Id3Frame) {
|
||||
Id3Frame id3Frame = (Id3Frame) entry;
|
||||
Log.d(TAG, String.format(" %s", id3Frame.id));
|
||||
Log.d(TAG, prefix + String.format("%s", id3Frame.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,7 +132,6 @@ import java.util.List;
|
||||
public static final int TYPE_vp08 = Util.getIntegerCodeForString("vp08");
|
||||
public static final int TYPE_vp09 = Util.getIntegerCodeForString("vp09");
|
||||
public static final int TYPE_vpcC = Util.getIntegerCodeForString("vpcC");
|
||||
public static final int TYPE_DASHES = Util.getIntegerCodeForString("----");
|
||||
|
||||
public final int type;
|
||||
|
||||
@ -299,7 +298,7 @@ import java.util.List;
|
||||
* @return The corresponding four character string.
|
||||
*/
|
||||
public static String getAtomTypeString(int type) {
|
||||
return "" + (char) (type >> 24)
|
||||
return "" + (char) ((type >> 24) & 0xFF)
|
||||
+ (char) ((type >> 16) & 0xFF)
|
||||
+ (char) ((type >> 8) & 0xFF)
|
||||
+ (char) (type & 0xFF);
|
||||
|
@ -24,11 +24,6 @@ import com.google.android.exoplayer2.audio.Ac3Util;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||
import com.google.android.exoplayer2.extractor.GaplessInfoHolder;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.id3.BinaryFrame;
|
||||
import com.google.android.exoplayer2.metadata.id3.CommentFrame;
|
||||
import com.google.android.exoplayer2.metadata.id3.Id3Frame;
|
||||
import com.google.android.exoplayer2.metadata.id3.Id3Util;
|
||||
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
@ -418,336 +413,45 @@ import java.util.List;
|
||||
ParsableByteArray udtaData = udtaAtom.data;
|
||||
udtaData.setPosition(Atom.HEADER_SIZE);
|
||||
while (udtaData.bytesLeft() >= Atom.HEADER_SIZE) {
|
||||
int atomPosition = udtaData.getPosition();
|
||||
int atomSize = udtaData.readInt();
|
||||
int atomType = udtaData.readInt();
|
||||
if (atomType == Atom.TYPE_meta) {
|
||||
udtaData.setPosition(udtaData.getPosition() - Atom.HEADER_SIZE);
|
||||
udtaData.setLimit(udtaData.getPosition() + atomSize);
|
||||
return parseMetaAtom(udtaData);
|
||||
udtaData.setPosition(atomPosition);
|
||||
return parseMetaAtom(udtaData, atomPosition + atomSize);
|
||||
}
|
||||
udtaData.skipBytes(atomSize - Atom.HEADER_SIZE);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Metadata parseMetaAtom(ParsableByteArray data) {
|
||||
data.skipBytes(Atom.FULL_HEADER_SIZE);
|
||||
ParsableByteArray ilst = new ParsableByteArray();
|
||||
while (data.bytesLeft() >= Atom.HEADER_SIZE) {
|
||||
int payloadSize = data.readInt() - Atom.HEADER_SIZE;
|
||||
int atomType = data.readInt();
|
||||
private static Metadata parseMetaAtom(ParsableByteArray meta, int limit) {
|
||||
meta.skipBytes(Atom.FULL_HEADER_SIZE);
|
||||
while (meta.getPosition() < limit) {
|
||||
int atomPosition = meta.getPosition();
|
||||
int atomSize = meta.readInt();
|
||||
int atomType = meta.readInt();
|
||||
if (atomType == Atom.TYPE_ilst) {
|
||||
ilst.reset(data.data, data.getPosition() + payloadSize);
|
||||
ilst.setPosition(data.getPosition());
|
||||
Metadata metadata = parseIlst(ilst);
|
||||
if (metadata != null) {
|
||||
return metadata;
|
||||
}
|
||||
meta.setPosition(atomPosition);
|
||||
return parseIlst(meta, atomPosition + atomSize);
|
||||
}
|
||||
data.skipBytes(payloadSize);
|
||||
meta.skipBytes(atomSize - Atom.HEADER_SIZE);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Metadata parseIlst(ParsableByteArray ilst) {
|
||||
private static Metadata parseIlst(ParsableByteArray ilst, int limit) {
|
||||
ilst.skipBytes(Atom.HEADER_SIZE);
|
||||
ArrayList<Metadata.Entry> entries = new ArrayList<>();
|
||||
while (ilst.bytesLeft() > 0) {
|
||||
int position = ilst.getPosition();
|
||||
int endPosition = position + ilst.readInt();
|
||||
int type = ilst.readInt();
|
||||
parseIlstElement(ilst, type, endPosition, entries);
|
||||
ilst.setPosition(endPosition);
|
||||
while (ilst.getPosition() < limit) {
|
||||
Metadata.Entry entry = MetadataUtil.parseIlstElement(ilst);
|
||||
if (entry != null) {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
return entries.isEmpty() ? null : new Metadata(entries);
|
||||
}
|
||||
|
||||
private static final String P1 = "\u00a9";
|
||||
private static final String P2 = "\ufffd";
|
||||
private static final int TYPE_NAME_1 = Util.getIntegerCodeForString(P1 + "nam");
|
||||
private static final int TYPE_NAME_2 = Util.getIntegerCodeForString(P2 + "nam");
|
||||
private static final int TYPE_NAME_3 = Util.getIntegerCodeForString(P1 + "trk");
|
||||
private static final int TYPE_NAME_4 = Util.getIntegerCodeForString(P2 + "trk");
|
||||
private static final int TYPE_COMMENT_1 = Util.getIntegerCodeForString(P1 + "cmt");
|
||||
private static final int TYPE_COMMENT_2 = Util.getIntegerCodeForString(P2 + "cmt");
|
||||
private static final int TYPE_YEAR_1 = Util.getIntegerCodeForString(P1 + "day");
|
||||
private static final int TYPE_YEAR_2 = Util.getIntegerCodeForString(P2 + "day");
|
||||
private static final int TYPE_ARTIST_1 = Util.getIntegerCodeForString(P1 + "ART");
|
||||
private static final int TYPE_ARTIST_2 = Util.getIntegerCodeForString(P2 + "ART");
|
||||
private static final int TYPE_ENCODER_1 = Util.getIntegerCodeForString(P1 + "too");
|
||||
private static final int TYPE_ENCODER_2 = Util.getIntegerCodeForString(P2 + "too");
|
||||
private static final int TYPE_ALBUM_1 = Util.getIntegerCodeForString(P1 + "alb");
|
||||
private static final int TYPE_ALBUM_2 = Util.getIntegerCodeForString(P2 + "alb");
|
||||
private static final int TYPE_COMPOSER_1 = Util.getIntegerCodeForString(P1 + "com");
|
||||
private static final int TYPE_COMPOSER_2 = Util.getIntegerCodeForString(P2 + "com");
|
||||
private static final int TYPE_COMPOSER_3 = Util.getIntegerCodeForString(P1 + "wrt");
|
||||
private static final int TYPE_COMPOSER_4 = Util.getIntegerCodeForString(P2 + "wrt");
|
||||
private static final int TYPE_LYRICS_1 = Util.getIntegerCodeForString(P1 + "lyr");
|
||||
private static final int TYPE_LYRICS_2 = Util.getIntegerCodeForString(P2 + "lyr");
|
||||
private static final int TYPE_GENRE_1 = Util.getIntegerCodeForString(P1 + "gen");
|
||||
private static final int TYPE_GENRE_2 = Util.getIntegerCodeForString(P2 + "gen");
|
||||
private static final int TYPE_STANDARD_GENRE = Util.getIntegerCodeForString("gnre");
|
||||
private static final int TYPE_GROUPING_1 = Util.getIntegerCodeForString(P1 + "grp");
|
||||
private static final int TYPE_GROUPING_2 = Util.getIntegerCodeForString(P2 + "grp");
|
||||
private static final int TYPE_DISK_NUMBER = Util.getIntegerCodeForString("disk");
|
||||
private static final int TYPE_TRACK_NUMBER = Util.getIntegerCodeForString("trkn");
|
||||
private static final int TYPE_TEMPO = Util.getIntegerCodeForString("tmpo");
|
||||
private static final int TYPE_COMPILATION = Util.getIntegerCodeForString("cpil");
|
||||
private static final int TYPE_ALBUM_ARTIST = Util.getIntegerCodeForString("aART");
|
||||
private static final int TYPE_SORT_TRACK_NAME = Util.getIntegerCodeForString("sonm");
|
||||
private static final int TYPE_SORT_ALBUM = Util.getIntegerCodeForString("soal");
|
||||
private static final int TYPE_SORT_ARTIST = Util.getIntegerCodeForString("soar");
|
||||
private static final int TYPE_SORT_ALBUM_ARTIST = Util.getIntegerCodeForString("soaa");
|
||||
private static final int TYPE_SORT_COMPOSER = Util.getIntegerCodeForString("soco");
|
||||
private static final int TYPE_SORT_SHOW = Util.getIntegerCodeForString("sosn");
|
||||
private static final int TYPE_GAPLESS_ALBUM = Util.getIntegerCodeForString("pgap");
|
||||
private static final int TYPE_SHOW = Util.getIntegerCodeForString("tvsh");
|
||||
|
||||
// TBD: covr = cover art, various account and iTunes specific attributes, more TV attributes
|
||||
|
||||
private static void parseIlstElement(ParsableByteArray ilst, int type, int endPosition,
|
||||
List<Metadata.Entry> builder) {
|
||||
if (type == TYPE_NAME_1 || type == TYPE_NAME_2 || type == TYPE_NAME_3 || type == TYPE_NAME_4) {
|
||||
parseTextAttribute(builder, "TIT2", ilst);
|
||||
} else if (type == TYPE_COMMENT_1 || type == TYPE_COMMENT_2) {
|
||||
parseCommentAttribute(builder, "COMM", ilst);
|
||||
} else if (type == TYPE_YEAR_1 || type == TYPE_YEAR_2) {
|
||||
parseTextAttribute(builder, "TDRC", ilst);
|
||||
} else if (type == TYPE_ARTIST_1 || type == TYPE_ARTIST_2) {
|
||||
parseTextAttribute(builder, "TPE1", ilst);
|
||||
} else if (type == TYPE_ENCODER_1 || type == TYPE_ENCODER_2) {
|
||||
parseTextAttribute(builder, "TSSE", ilst);
|
||||
} else if (type == TYPE_ALBUM_1 || type == TYPE_ALBUM_2) {
|
||||
parseTextAttribute(builder, "TALB", ilst);
|
||||
} else if (type == TYPE_COMPOSER_1 || type == TYPE_COMPOSER_2 ||
|
||||
type == TYPE_COMPOSER_3 || type == TYPE_COMPOSER_4) {
|
||||
parseTextAttribute(builder, "TCOM", ilst);
|
||||
} else if (type == TYPE_LYRICS_1 || type == TYPE_LYRICS_2) {
|
||||
parseTextAttribute(builder, "lyrics", ilst);
|
||||
} else if (type == TYPE_STANDARD_GENRE) {
|
||||
parseStandardGenreAttribute(builder, "TCON", ilst);
|
||||
} else if (type == TYPE_GENRE_1 || type == TYPE_GENRE_2) {
|
||||
parseTextAttribute(builder, "TCON", ilst);
|
||||
} else if (type == TYPE_GROUPING_1 || type == TYPE_GROUPING_2) {
|
||||
parseTextAttribute(builder, "TIT1", ilst);
|
||||
} else if (type == TYPE_DISK_NUMBER) {
|
||||
parseIndexAndCountAttribute(builder, "TPOS", ilst, endPosition);
|
||||
} else if (type == TYPE_TRACK_NUMBER) {
|
||||
parseIndexAndCountAttribute(builder, "TRCK", ilst, endPosition);
|
||||
} else if (type == TYPE_TEMPO) {
|
||||
parseIntegerAttribute(builder, "TBPM", ilst);
|
||||
} else if (type == TYPE_COMPILATION) {
|
||||
parseBooleanAttribute(builder, "TCMP", ilst);
|
||||
} else if (type == TYPE_ALBUM_ARTIST) {
|
||||
parseTextAttribute(builder, "TPE2", ilst);
|
||||
} else if (type == TYPE_SORT_TRACK_NAME) {
|
||||
parseTextAttribute(builder, "TSOT", ilst);
|
||||
} else if (type == TYPE_SORT_ALBUM) {
|
||||
parseTextAttribute(builder, "TSO2", ilst);
|
||||
} else if (type == TYPE_SORT_ARTIST) {
|
||||
parseTextAttribute(builder, "TSOA", ilst);
|
||||
} else if (type == TYPE_SORT_ALBUM_ARTIST) {
|
||||
parseTextAttribute(builder, "TSOP", ilst);
|
||||
} else if (type == TYPE_SORT_COMPOSER) {
|
||||
parseTextAttribute(builder, "TSOC", ilst);
|
||||
} else if (type == TYPE_SORT_SHOW) {
|
||||
parseTextAttribute(builder, "sortShow", ilst);
|
||||
} else if (type == TYPE_GAPLESS_ALBUM) {
|
||||
parseBooleanAttribute(builder, "gaplessAlbum", ilst);
|
||||
} else if (type == TYPE_SHOW) {
|
||||
parseTextAttribute(builder, "show", ilst);
|
||||
} else if (type == Atom.TYPE_DASHES) {
|
||||
parseExtendedAttribute(builder, ilst, endPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseTextAttribute(List<Metadata.Entry> builder, String attributeName,
|
||||
ParsableByteArray ilst) {
|
||||
int length = ilst.readInt() - Atom.FULL_HEADER_SIZE;
|
||||
int key = ilst.readInt();
|
||||
ilst.skipBytes(4);
|
||||
if (key == Atom.TYPE_data) {
|
||||
ilst.skipBytes(4);
|
||||
String value = ilst.readNullTerminatedString(length - 4);
|
||||
Id3Frame frame = new TextInformationFrame(attributeName, value);
|
||||
builder.add(frame);
|
||||
} else {
|
||||
ilst.skipBytes(length);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseCommentAttribute(List<Metadata.Entry> builder, String attributeName,
|
||||
ParsableByteArray ilst) {
|
||||
int length = ilst.readInt() - Atom.FULL_HEADER_SIZE;
|
||||
int key = ilst.readInt();
|
||||
ilst.skipBytes(4);
|
||||
if (key == Atom.TYPE_data) {
|
||||
ilst.skipBytes(4);
|
||||
String value = ilst.readNullTerminatedString(length - 4);
|
||||
Id3Frame frame = new CommentFrame("eng", attributeName, value);
|
||||
builder.add(frame);
|
||||
} else {
|
||||
ilst.skipBytes(length);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseBooleanAttribute(List<Metadata.Entry> builder, String attributeName,
|
||||
ParsableByteArray ilst) {
|
||||
int length = ilst.readInt() - Atom.FULL_HEADER_SIZE;
|
||||
int key = ilst.readInt();
|
||||
ilst.skipBytes(4);
|
||||
if (key == Atom.TYPE_data) {
|
||||
Object value = parseDataBox(ilst, length);
|
||||
if (value instanceof Integer) {
|
||||
int n = (Integer) value;
|
||||
String s = n == 0 ? "0" : "1";
|
||||
Id3Frame frame = new TextInformationFrame(attributeName, s);
|
||||
builder.add(frame);
|
||||
}
|
||||
} else {
|
||||
ilst.skipBytes(length);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseIntegerAttribute(List<Metadata.Entry> builder, String attributeName,
|
||||
ParsableByteArray ilst) {
|
||||
int length = ilst.readInt() - Atom.FULL_HEADER_SIZE;
|
||||
int key = ilst.readInt();
|
||||
ilst.skipBytes(4);
|
||||
if (key == Atom.TYPE_data) {
|
||||
Object value = parseDataBox(ilst, length);
|
||||
if (value instanceof Integer) {
|
||||
int n = (Integer) value;
|
||||
String s = "" + n;
|
||||
Id3Frame frame = new TextInformationFrame(attributeName, s);
|
||||
builder.add(frame);
|
||||
}
|
||||
} else {
|
||||
ilst.skipBytes(length);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseIndexAndCountAttribute(List<Metadata.Entry> builder,
|
||||
String attributeName, ParsableByteArray ilst, int endPosition) {
|
||||
int length = ilst.readInt() - Atom.FULL_HEADER_SIZE;
|
||||
int key = ilst.readInt();
|
||||
ilst.skipBytes(4);
|
||||
if (key == Atom.TYPE_data) {
|
||||
Object value = parseDataBox(ilst, length);
|
||||
if (value instanceof byte[]) {
|
||||
byte[] bytes = (byte[]) value;
|
||||
if (bytes.length == 8) {
|
||||
int index = (bytes[2] << 8) + (bytes[3] & 0xFF);
|
||||
int count = (bytes[4] << 8) + (bytes[5] & 0xFF);
|
||||
if (index > 0) {
|
||||
String s = "" + index;
|
||||
if (count > 0) {
|
||||
s = s + "/" + count;
|
||||
}
|
||||
Id3Frame frame = new TextInformationFrame(attributeName, s);
|
||||
builder.add(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ilst.skipBytes(length);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseStandardGenreAttribute(List<Metadata.Entry> builder,
|
||||
String attributeName, ParsableByteArray ilst) {
|
||||
int length = ilst.readInt() - Atom.FULL_HEADER_SIZE;
|
||||
int key = ilst.readInt();
|
||||
ilst.skipBytes(4);
|
||||
if (key == Atom.TYPE_data) {
|
||||
Object value = parseDataBox(ilst, length);
|
||||
if (value instanceof byte[]) {
|
||||
byte[] bytes = (byte[]) value;
|
||||
if (bytes.length == 2) {
|
||||
int code = (bytes[0] << 8) + (bytes[1] & 0xFF);
|
||||
String s = Id3Util.decodeGenre(code);
|
||||
if (s != null) {
|
||||
Id3Frame frame = new TextInformationFrame(attributeName, s);
|
||||
builder.add(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ilst.skipBytes(length);
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseExtendedAttribute(List<Metadata.Entry> builder, ParsableByteArray ilst,
|
||||
int endPosition) {
|
||||
String domain = null;
|
||||
String name = null;
|
||||
Object value = null;
|
||||
|
||||
while (ilst.getPosition() < endPosition) {
|
||||
int length = ilst.readInt() - Atom.FULL_HEADER_SIZE;
|
||||
int key = ilst.readInt();
|
||||
ilst.skipBytes(4);
|
||||
if (key == Atom.TYPE_mean) {
|
||||
domain = ilst.readNullTerminatedString(length);
|
||||
} else if (key == Atom.TYPE_name) {
|
||||
name = ilst.readNullTerminatedString(length);
|
||||
} else if (key == Atom.TYPE_data) {
|
||||
value = parseDataBox(ilst, length);
|
||||
} else {
|
||||
ilst.skipBytes(length);
|
||||
}
|
||||
}
|
||||
|
||||
if (value != null) {
|
||||
if (Util.areEqual(domain, "com.apple.iTunes")) {
|
||||
String s = new String((byte[]) value);
|
||||
Id3Frame frame = new CommentFrame("eng", name, s);
|
||||
builder.add(frame);
|
||||
} else if (domain != null && name != null) {
|
||||
String extendedName = domain + "." + name;
|
||||
if (value instanceof String) {
|
||||
Id3Frame frame = new TextInformationFrame(extendedName, (String) value);
|
||||
builder.add(frame);
|
||||
} else if (value instanceof Integer) {
|
||||
Id3Frame frame = new TextInformationFrame(extendedName, value.toString());
|
||||
builder.add(frame);
|
||||
} else if (value instanceof byte[]) {
|
||||
byte[] bb = (byte[]) value;
|
||||
Id3Frame frame = new BinaryFrame(extendedName, bb);
|
||||
builder.add(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Object parseDataBox(ParsableByteArray ilst, int length) {
|
||||
int versionAndFlags = ilst.readInt();
|
||||
int flags = versionAndFlags & 0xFFFFFF;
|
||||
boolean isText = (flags == 1);
|
||||
boolean isData = (flags == 0);
|
||||
boolean isImageData = (flags == 0xD);
|
||||
boolean isInteger = (flags == 21);
|
||||
int dataLength = length - 4;
|
||||
if (isText) {
|
||||
return ilst.readNullTerminatedString(dataLength);
|
||||
} else if (isInteger) {
|
||||
if (dataLength == 1) {
|
||||
return ilst.readUnsignedByte();
|
||||
} else if (dataLength == 2) {
|
||||
return ilst.readUnsignedShort();
|
||||
} else {
|
||||
ilst.skipBytes(dataLength);
|
||||
return null;
|
||||
}
|
||||
} else if (isData) {
|
||||
byte[] bytes = new byte[dataLength];
|
||||
ilst.readBytes(bytes, 0, dataLength);
|
||||
return bytes;
|
||||
} else {
|
||||
ilst.skipBytes(dataLength);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a mvhd atom (defined in 14496-12), returning the timescale for the movie.
|
||||
*
|
||||
@ -756,12 +460,9 @@ import java.util.List;
|
||||
*/
|
||||
private static long parseMvhd(ParsableByteArray mvhd) {
|
||||
mvhd.setPosition(Atom.HEADER_SIZE);
|
||||
|
||||
int fullAtom = mvhd.readInt();
|
||||
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||
|
||||
mvhd.skipBytes(version == 0 ? 8 : 16);
|
||||
|
||||
return mvhd.readUnsignedInt();
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.extractor.mp4;
|
||||
|
||||
import android.util.Log;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.id3.ApicFrame;
|
||||
import com.google.android.exoplayer2.metadata.id3.CommentFrame;
|
||||
import com.google.android.exoplayer2.metadata.id3.Id3Frame;
|
||||
import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/**
|
||||
* Parses metadata items stored in ilst atoms.
|
||||
*/
|
||||
/* package */ final class MetadataUtil {
|
||||
|
||||
private static final String TAG = "MetadataUtil";
|
||||
|
||||
// Codes that start with the copyright character (omitted) and have equivalent ID3 frames.
|
||||
private static final int SHORT_TYPE_NAME_1 = Util.getIntegerCodeForString("nam");
|
||||
private static final int SHORT_TYPE_NAME_2 = Util.getIntegerCodeForString("trk");
|
||||
private static final int SHORT_TYPE_COMMENT = Util.getIntegerCodeForString("cmt");
|
||||
private static final int SHORT_TYPE_YEAR = Util.getIntegerCodeForString("day");
|
||||
private static final int SHORT_TYPE_ARTIST = Util.getIntegerCodeForString("ART");
|
||||
private static final int SHORT_TYPE_ENCODER = Util.getIntegerCodeForString("too");
|
||||
private static final int SHORT_TYPE_ALBUM = Util.getIntegerCodeForString("alb");
|
||||
private static final int SHORT_TYPE_COMPOSER_1 = Util.getIntegerCodeForString("com");
|
||||
private static final int SHORT_TYPE_COMPOSER_2 = Util.getIntegerCodeForString("wrt");
|
||||
private static final int SHORT_TYPE_LYRICS = Util.getIntegerCodeForString("lyr");
|
||||
private static final int SHORT_TYPE_GENRE = Util.getIntegerCodeForString("gen");
|
||||
|
||||
// Codes that have equivalent ID3 frames.
|
||||
private static final int TYPE_COVER_ART = Util.getIntegerCodeForString("covr");
|
||||
private static final int TYPE_GENRE = Util.getIntegerCodeForString("gnre");
|
||||
private static final int TYPE_GROUPING = Util.getIntegerCodeForString("grp");
|
||||
private static final int TYPE_DISK_NUMBER = Util.getIntegerCodeForString("disk");
|
||||
private static final int TYPE_TRACK_NUMBER = Util.getIntegerCodeForString("trkn");
|
||||
private static final int TYPE_TEMPO = Util.getIntegerCodeForString("tmpo");
|
||||
private static final int TYPE_COMPILATION = Util.getIntegerCodeForString("cpil");
|
||||
private static final int TYPE_ALBUM_ARTIST = Util.getIntegerCodeForString("aART");
|
||||
private static final int TYPE_SORT_TRACK_NAME = Util.getIntegerCodeForString("sonm");
|
||||
private static final int TYPE_SORT_ALBUM = Util.getIntegerCodeForString("soal");
|
||||
private static final int TYPE_SORT_ARTIST = Util.getIntegerCodeForString("soar");
|
||||
private static final int TYPE_SORT_ALBUM_ARTIST = Util.getIntegerCodeForString("soaa");
|
||||
private static final int TYPE_SORT_COMPOSER = Util.getIntegerCodeForString("soco");
|
||||
|
||||
// Types that do not have equivalent ID3 frames.
|
||||
private static final int TYPE_RATING = Util.getIntegerCodeForString("rtng");
|
||||
private static final int TYPE_GAPLESS_ALBUM = Util.getIntegerCodeForString("pgap");
|
||||
private static final int TYPE_TV_SORT_SHOW = Util.getIntegerCodeForString("sosn");
|
||||
private static final int TYPE_TV_SHOW = Util.getIntegerCodeForString("tvsh");
|
||||
|
||||
// Type for items that are intended for internal use by the player.
|
||||
private static final int TYPE_INTERNAL = Util.getIntegerCodeForString("----");
|
||||
|
||||
// Standard genres.
|
||||
private static final String[] STANDARD_GENRES = new String[] {
|
||||
// These are the official ID3v1 genres.
|
||||
"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
|
||||
"Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap",
|
||||
"Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska",
|
||||
"Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient",
|
||||
"Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical",
|
||||
"Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise",
|
||||
"AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative",
|
||||
"Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave",
|
||||
"Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream",
|
||||
"Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap",
|
||||
"Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave",
|
||||
"Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal",
|
||||
"Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll",
|
||||
"Hard Rock",
|
||||
// These were made up by the authors of Winamp but backported into the ID3 spec.
|
||||
"Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion",
|
||||
"Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde",
|
||||
"Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock",
|
||||
"Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour",
|
||||
"Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony",
|
||||
"Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club",
|
||||
"Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul",
|
||||
"Freestyle", "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House",
|
||||
"Dance Hall",
|
||||
// These were also invented by the Winamp folks but ignored by the ID3 authors.
|
||||
"Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie",
|
||||
"BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap",
|
||||
"Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian",
|
||||
"Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "Jpop",
|
||||
"Synthpop"
|
||||
};
|
||||
|
||||
private static final String LANGUAGE_UNDEFINED = "und";
|
||||
|
||||
private MetadataUtil() {}
|
||||
|
||||
/**
|
||||
* Parses a single ilst element from a {@link ParsableByteArray}. The element is read starting
|
||||
* from the current position of the {@link ParsableByteArray}, and the position is advanced by
|
||||
* the size of the element. The position is advanced even if the element's type is unrecognized.
|
||||
*
|
||||
* @param ilst Holds the data to be parsed.
|
||||
* @return The parsed element, or null if the element's type was not recognized.
|
||||
*/
|
||||
public static Metadata.Entry parseIlstElement(ParsableByteArray ilst) {
|
||||
int position = ilst.getPosition();
|
||||
int endPosition = position + ilst.readInt();
|
||||
int type = ilst.readInt();
|
||||
int typeTopByte = (type >> 24) & 0xFF;
|
||||
try {
|
||||
if (typeTopByte == '\u00A9' /* Copyright char */
|
||||
|| typeTopByte == '\uFFFD' /* Replacement char */) {
|
||||
int shortType = type & 0x00FFFFFF;
|
||||
if (shortType == SHORT_TYPE_COMMENT) {
|
||||
return parseCommentAttribute(type, ilst);
|
||||
} else if (shortType == SHORT_TYPE_NAME_1 || shortType == SHORT_TYPE_NAME_2) {
|
||||
return parseTextAttribute(type, "TIT2", ilst);
|
||||
} else if (shortType == SHORT_TYPE_COMPOSER_1 || shortType == SHORT_TYPE_COMPOSER_2) {
|
||||
return parseTextAttribute(type, "TCOM", ilst);
|
||||
} else if (shortType == SHORT_TYPE_YEAR) {
|
||||
return parseTextAttribute(type, "TDRC", ilst);
|
||||
} else if (shortType == SHORT_TYPE_ARTIST) {
|
||||
return parseTextAttribute(type, "TPE1", ilst);
|
||||
} else if (shortType == SHORT_TYPE_ENCODER) {
|
||||
return parseTextAttribute(type, "TSSE", ilst);
|
||||
} else if (shortType == SHORT_TYPE_ALBUM) {
|
||||
return parseTextAttribute(type, "TALB", ilst);
|
||||
} else if (shortType == SHORT_TYPE_LYRICS) {
|
||||
return parseTextAttribute(type, "USLT", ilst);
|
||||
} else if (shortType == SHORT_TYPE_GENRE) {
|
||||
return parseTextAttribute(type, "TCON", ilst);
|
||||
} else if (shortType == TYPE_GROUPING) {
|
||||
return parseTextAttribute(type, "TIT1", ilst);
|
||||
}
|
||||
} else if (type == TYPE_GENRE) {
|
||||
return parseStandardGenreAttribute(ilst);
|
||||
} else if (type == TYPE_DISK_NUMBER) {
|
||||
return parseIndexAndCountAttribute(type, "TPOS", ilst);
|
||||
} else if (type == TYPE_TRACK_NUMBER) {
|
||||
return parseIndexAndCountAttribute(type, "TRCK", ilst);
|
||||
} else if (type == TYPE_TEMPO) {
|
||||
return parseUint8Attribute(type, "TBPM", ilst, true, false);
|
||||
} else if (type == TYPE_COMPILATION) {
|
||||
return parseUint8Attribute(type, "TCMP", ilst, true, true);
|
||||
} else if (type == TYPE_COVER_ART) {
|
||||
return parseCoverArt(ilst);
|
||||
} else if (type == TYPE_ALBUM_ARTIST) {
|
||||
return parseTextAttribute(type, "TPE2", ilst);
|
||||
} else if (type == TYPE_SORT_TRACK_NAME) {
|
||||
return parseTextAttribute(type, "TSOT", ilst);
|
||||
} else if (type == TYPE_SORT_ALBUM) {
|
||||
return parseTextAttribute(type, "TSO2", ilst);
|
||||
} else if (type == TYPE_SORT_ARTIST) {
|
||||
return parseTextAttribute(type, "TSOA", ilst);
|
||||
} else if (type == TYPE_SORT_ALBUM_ARTIST) {
|
||||
return parseTextAttribute(type, "TSOP", ilst);
|
||||
} else if (type == TYPE_SORT_COMPOSER) {
|
||||
return parseTextAttribute(type, "TSOC", ilst);
|
||||
} else if (type == TYPE_RATING) {
|
||||
return parseUint8Attribute(type, "ITUNESADVISORY", ilst, false, false);
|
||||
} else if (type == TYPE_GAPLESS_ALBUM) {
|
||||
return parseUint8Attribute(type, "ITUNESGAPLESS", ilst, false, true);
|
||||
} else if (type == TYPE_TV_SORT_SHOW) {
|
||||
return parseTextAttribute(type, "TVSHOWSORT", ilst);
|
||||
} else if (type == TYPE_TV_SHOW) {
|
||||
return parseTextAttribute(type, "TVSHOW", ilst);
|
||||
} else if (type == TYPE_INTERNAL) {
|
||||
return parseInternalAttribute(ilst, endPosition);
|
||||
}
|
||||
Log.d(TAG, "Skipped unknown metadata entry: " + Atom.getAtomTypeString(type));
|
||||
return null;
|
||||
} finally {
|
||||
ilst.setPosition(endPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private static TextInformationFrame parseTextAttribute(int type, String id,
|
||||
ParsableByteArray data) {
|
||||
int atomSize = data.readInt();
|
||||
int atomType = data.readInt();
|
||||
if (atomType == Atom.TYPE_data) {
|
||||
data.skipBytes(8); // version (1), flags (3), empty (4)
|
||||
String value = data.readNullTerminatedString(atomSize - 16);
|
||||
return new TextInformationFrame(id, value);
|
||||
}
|
||||
Log.w(TAG, "Failed to parse text attribute: " + Atom.getAtomTypeString(type));
|
||||
return null;
|
||||
}
|
||||
|
||||
private static CommentFrame parseCommentAttribute(int type, ParsableByteArray data) {
|
||||
int atomSize = data.readInt();
|
||||
int atomType = data.readInt();
|
||||
if (atomType == Atom.TYPE_data) {
|
||||
data.skipBytes(8); // version (1), flags (3), empty (4)
|
||||
String value = data.readNullTerminatedString(atomSize - 16);
|
||||
return new CommentFrame(LANGUAGE_UNDEFINED, value, value);
|
||||
}
|
||||
Log.w(TAG, "Failed to parse comment attribute: " + Atom.getAtomTypeString(type));
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Id3Frame parseUint8Attribute(int type, String id, ParsableByteArray data,
|
||||
boolean isTextInformationFrame, boolean isBoolean) {
|
||||
int value = parseUint8AttributeValue(data);
|
||||
if (isBoolean) {
|
||||
value = Math.min(1, value);
|
||||
}
|
||||
if (value >= 0) {
|
||||
return isTextInformationFrame ? new TextInformationFrame(id, Integer.toString(value))
|
||||
: new CommentFrame(LANGUAGE_UNDEFINED, id, Integer.toString(value));
|
||||
}
|
||||
Log.w(TAG, "Failed to parse uint8 attribute: " + Atom.getAtomTypeString(type));
|
||||
return null;
|
||||
}
|
||||
|
||||
private static TextInformationFrame parseIndexAndCountAttribute(int type, String attributeName,
|
||||
ParsableByteArray data) {
|
||||
int atomSize = data.readInt();
|
||||
int atomType = data.readInt();
|
||||
if (atomType == Atom.TYPE_data && atomSize >= 22) {
|
||||
data.skipBytes(10); // version (1), flags (3), empty (4), empty (2)
|
||||
int index = data.readUnsignedShort();
|
||||
if (index > 0) {
|
||||
String description = "" + index;
|
||||
int count = data.readUnsignedShort();
|
||||
if (count > 0) {
|
||||
description += "/" + count;
|
||||
}
|
||||
return new TextInformationFrame(attributeName, description);
|
||||
}
|
||||
}
|
||||
Log.w(TAG, "Failed to parse index/count attribute: " + Atom.getAtomTypeString(type));
|
||||
return null;
|
||||
}
|
||||
|
||||
private static TextInformationFrame parseStandardGenreAttribute(ParsableByteArray data) {
|
||||
int genreCode = parseUint8AttributeValue(data);
|
||||
String genreString = (0 < genreCode && genreCode <= STANDARD_GENRES.length)
|
||||
? STANDARD_GENRES[genreCode - 1] : null;
|
||||
if (genreString != null) {
|
||||
return new TextInformationFrame("TCON", genreString);
|
||||
}
|
||||
Log.w(TAG, "Failed to parse standard genre code");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ApicFrame parseCoverArt(ParsableByteArray data) {
|
||||
int atomSize = data.readInt();
|
||||
int atomType = data.readInt();
|
||||
if (atomType == Atom.TYPE_data) {
|
||||
int fullVersionInt = data.readInt();
|
||||
int flags = Atom.parseFullAtomFlags(fullVersionInt);
|
||||
String mimeType = flags == 13 ? "image/jpeg" : flags == 14 ? "image/png" : null;
|
||||
if (mimeType == null) {
|
||||
Log.w(TAG, "Unrecognized cover art flags: " + flags);
|
||||
return null;
|
||||
}
|
||||
data.skipBytes(4); // empty (4)
|
||||
byte[] pictureData = new byte[atomSize - 16];
|
||||
data.readBytes(pictureData, 0, pictureData.length);
|
||||
return new ApicFrame(mimeType, null, 3 /* Cover (front) */, pictureData);
|
||||
}
|
||||
Log.w(TAG, "Failed to parse cover art attribute");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Id3Frame parseInternalAttribute(ParsableByteArray data, int endPosition) {
|
||||
String domain = null;
|
||||
String name = null;
|
||||
int dataAtomPosition = -1;
|
||||
int dataAtomSize = -1;
|
||||
while (data.getPosition() < endPosition) {
|
||||
int atomPosition = data.getPosition();
|
||||
int atomSize = data.readInt();
|
||||
int atomType = data.readInt();
|
||||
data.skipBytes(4); // version (1), flags (3)
|
||||
if (atomType == Atom.TYPE_mean) {
|
||||
domain = data.readNullTerminatedString(atomSize - 12);
|
||||
} else if (atomType == Atom.TYPE_name) {
|
||||
name = data.readNullTerminatedString(atomSize - 12);
|
||||
} else {
|
||||
if (atomType == Atom.TYPE_data) {
|
||||
dataAtomPosition = atomPosition;
|
||||
dataAtomSize = atomSize;
|
||||
}
|
||||
data.skipBytes(atomSize - 12);
|
||||
}
|
||||
}
|
||||
if (!"com.apple.iTunes".equals(domain) || !"iTunSMPB".equals(name) || dataAtomPosition == -1) {
|
||||
// We're only interested in iTunSMPB.
|
||||
return null;
|
||||
}
|
||||
data.setPosition(dataAtomPosition);
|
||||
data.skipBytes(16); // size (4), type (4), version (1), flags (3), empty (4)
|
||||
String value = data.readNullTerminatedString(dataAtomSize - 16);
|
||||
return new CommentFrame(LANGUAGE_UNDEFINED, name, value);
|
||||
}
|
||||
|
||||
private static int parseUint8AttributeValue(ParsableByteArray data) {
|
||||
data.skipBytes(4); // atomSize
|
||||
int atomType = data.readInt();
|
||||
if (atomType == Atom.TYPE_data) {
|
||||
data.skipBytes(8); // version (1), flags (3), empty (4)
|
||||
return data.readUnsignedByte();
|
||||
}
|
||||
Log.w(TAG, "Failed to parse uint8 attribute value");
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.metadata.id3;
|
||||
|
||||
/**
|
||||
* ID3 utility methods.
|
||||
*/
|
||||
public final class Id3Util {
|
||||
|
||||
private static final String[] STANDARD_GENRES = new String[] {
|
||||
// These are the official ID3v1 genres.
|
||||
"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
|
||||
"Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap",
|
||||
"Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska",
|
||||
"Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient",
|
||||
"Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical",
|
||||
"Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise",
|
||||
"AlternRock", "Bass", "Soul", "Punk", "Space", "Meditative",
|
||||
"Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic", "Darkwave",
|
||||
"Techno-Industrial", "Electronic", "Pop-Folk", "Eurodance", "Dream",
|
||||
"Southern Rock", "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap",
|
||||
"Pop/Funk", "Jungle", "Native American", "Cabaret", "New Wave",
|
||||
"Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal",
|
||||
"Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll",
|
||||
"Hard Rock",
|
||||
// These were made up by the authors of Winamp but backported into the ID3 spec.
|
||||
"Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion",
|
||||
"Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde",
|
||||
"Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock",
|
||||
"Slow Rock", "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour",
|
||||
"Speech", "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony",
|
||||
"Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam", "Club",
|
||||
"Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul",
|
||||
"Freestyle", "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House",
|
||||
"Dance Hall",
|
||||
// These were also invented by the Winamp folks but ignored by the ID3 authors.
|
||||
"Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie",
|
||||
"BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap",
|
||||
"Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian",
|
||||
"Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "Jpop",
|
||||
"Synthpop"
|
||||
};
|
||||
|
||||
private Id3Util() {}
|
||||
|
||||
public static String decodeGenre(int code) {
|
||||
return (0 < code && code <= STANDARD_GENRES.length) ? STANDARD_GENRES[code - 1] : null;
|
||||
}
|
||||
|
||||
}
|
@ -300,9 +300,9 @@ public final class ParsableByteArray {
|
||||
*/
|
||||
public int readLittleEndianInt() {
|
||||
return (data[position++] & 0xFF)
|
||||
| (data[position++] & 0xFF) << 8
|
||||
| (data[position++] & 0xFF) << 16
|
||||
| (data[position++] & 0xFF) << 24;
|
||||
| (data[position++] & 0xFF) << 8
|
||||
| (data[position++] & 0xFF) << 16
|
||||
| (data[position++] & 0xFF) << 24;
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user