mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Big cleanup of mp4 metadata extraction
This commit is contained in:
parent
1b39d21ed4
commit
8caaf0b5d9
6 changed files with 369 additions and 402 deletions
|
|
@ -154,6 +154,18 @@ import java.util.Locale;
|
||||||
}
|
}
|
||||||
Log.d(TAG, " ]");
|
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, " ]");
|
Log.d(TAG, " ]");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,7 +196,7 @@ import java.util.Locale;
|
||||||
@Override
|
@Override
|
||||||
public void onMetadata(Metadata metadata) {
|
public void onMetadata(Metadata metadata) {
|
||||||
Log.d(TAG, "onMetadata [");
|
Log.d(TAG, "onMetadata [");
|
||||||
printMetadata(metadata);
|
printMetadata(metadata, " ");
|
||||||
Log.d(TAG, "]");
|
Log.d(TAG, "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,13 +220,8 @@ import java.util.Locale;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAudioInputFormatChanged(Format format) {
|
public void onAudioInputFormatChanged(Format format) {
|
||||||
boolean hasMetadata = format.metadata != null;
|
|
||||||
Log.d(TAG, "audioFormatChanged [" + getSessionTimeString() + ", " + getFormatString(format)
|
Log.d(TAG, "audioFormatChanged [" + getSessionTimeString() + ", " + getFormatString(format)
|
||||||
+ (hasMetadata ? "" : "]"));
|
+ "]");
|
||||||
if (hasMetadata) {
|
|
||||||
printMetadata(format.metadata);
|
|
||||||
Log.d(TAG, "]");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -335,35 +342,35 @@ import java.util.Locale;
|
||||||
Log.e(TAG, "internalError [" + getSessionTimeString() + ", " + type + "]", e);
|
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++) {
|
for (int i = 0; i < metadata.length(); i++) {
|
||||||
Metadata.Entry entry = metadata.get(i);
|
Metadata.Entry entry = metadata.get(i);
|
||||||
if (entry instanceof TxxxFrame) {
|
if (entry instanceof TxxxFrame) {
|
||||||
TxxxFrame txxxFrame = (TxxxFrame) entry;
|
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));
|
txxxFrame.description, txxxFrame.value));
|
||||||
} else if (entry instanceof PrivFrame) {
|
} else if (entry instanceof PrivFrame) {
|
||||||
PrivFrame privFrame = (PrivFrame) entry;
|
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) {
|
} else if (entry instanceof GeobFrame) {
|
||||||
GeobFrame geobFrame = (GeobFrame) entry;
|
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));
|
geobFrame.id, geobFrame.mimeType, geobFrame.filename, geobFrame.description));
|
||||||
} else if (entry instanceof ApicFrame) {
|
} else if (entry instanceof ApicFrame) {
|
||||||
ApicFrame apicFrame = (ApicFrame) entry;
|
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));
|
apicFrame.id, apicFrame.mimeType, apicFrame.description));
|
||||||
} else if (entry instanceof TextInformationFrame) {
|
} else if (entry instanceof TextInformationFrame) {
|
||||||
TextInformationFrame textInformationFrame = (TextInformationFrame) entry;
|
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));
|
textInformationFrame.description));
|
||||||
} else if (entry instanceof CommentFrame) {
|
} else if (entry instanceof CommentFrame) {
|
||||||
CommentFrame commentFrame = (CommentFrame) entry;
|
CommentFrame commentFrame = (CommentFrame) entry;
|
||||||
Log.d(TAG, String.format(" %s: language=%s description=%s", commentFrame.id,
|
Log.d(TAG, prefix + String.format("%s: language=%s description=%s", commentFrame.id,
|
||||||
commentFrame.language, commentFrame.description));
|
commentFrame.language, commentFrame.description, commentFrame.text));
|
||||||
} else if (entry instanceof Id3Frame) {
|
} else if (entry instanceof Id3Frame) {
|
||||||
Id3Frame id3Frame = (Id3Frame) entry;
|
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_vp08 = Util.getIntegerCodeForString("vp08");
|
||||||
public static final int TYPE_vp09 = Util.getIntegerCodeForString("vp09");
|
public static final int TYPE_vp09 = Util.getIntegerCodeForString("vp09");
|
||||||
public static final int TYPE_vpcC = Util.getIntegerCodeForString("vpcC");
|
public static final int TYPE_vpcC = Util.getIntegerCodeForString("vpcC");
|
||||||
public static final int TYPE_DASHES = Util.getIntegerCodeForString("----");
|
|
||||||
|
|
||||||
public final int type;
|
public final int type;
|
||||||
|
|
||||||
|
|
@ -299,7 +298,7 @@ import java.util.List;
|
||||||
* @return The corresponding four character string.
|
* @return The corresponding four character string.
|
||||||
*/
|
*/
|
||||||
public static String getAtomTypeString(int type) {
|
public static String getAtomTypeString(int type) {
|
||||||
return "" + (char) (type >> 24)
|
return "" + (char) ((type >> 24) & 0xFF)
|
||||||
+ (char) ((type >> 16) & 0xFF)
|
+ (char) ((type >> 16) & 0xFF)
|
||||||
+ (char) ((type >> 8) & 0xFF)
|
+ (char) ((type >> 8) & 0xFF)
|
||||||
+ (char) (type & 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.drm.DrmInitData;
|
||||||
import com.google.android.exoplayer2.extractor.GaplessInfoHolder;
|
import com.google.android.exoplayer2.extractor.GaplessInfoHolder;
|
||||||
import com.google.android.exoplayer2.metadata.Metadata;
|
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.Assertions;
|
||||||
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
|
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
|
@ -418,336 +413,45 @@ import java.util.List;
|
||||||
ParsableByteArray udtaData = udtaAtom.data;
|
ParsableByteArray udtaData = udtaAtom.data;
|
||||||
udtaData.setPosition(Atom.HEADER_SIZE);
|
udtaData.setPosition(Atom.HEADER_SIZE);
|
||||||
while (udtaData.bytesLeft() >= Atom.HEADER_SIZE) {
|
while (udtaData.bytesLeft() >= Atom.HEADER_SIZE) {
|
||||||
|
int atomPosition = udtaData.getPosition();
|
||||||
int atomSize = udtaData.readInt();
|
int atomSize = udtaData.readInt();
|
||||||
int atomType = udtaData.readInt();
|
int atomType = udtaData.readInt();
|
||||||
if (atomType == Atom.TYPE_meta) {
|
if (atomType == Atom.TYPE_meta) {
|
||||||
udtaData.setPosition(udtaData.getPosition() - Atom.HEADER_SIZE);
|
udtaData.setPosition(atomPosition);
|
||||||
udtaData.setLimit(udtaData.getPosition() + atomSize);
|
return parseMetaAtom(udtaData, atomPosition + atomSize);
|
||||||
return parseMetaAtom(udtaData);
|
|
||||||
}
|
}
|
||||||
udtaData.skipBytes(atomSize - Atom.HEADER_SIZE);
|
udtaData.skipBytes(atomSize - Atom.HEADER_SIZE);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Metadata parseMetaAtom(ParsableByteArray data) {
|
private static Metadata parseMetaAtom(ParsableByteArray meta, int limit) {
|
||||||
data.skipBytes(Atom.FULL_HEADER_SIZE);
|
meta.skipBytes(Atom.FULL_HEADER_SIZE);
|
||||||
ParsableByteArray ilst = new ParsableByteArray();
|
while (meta.getPosition() < limit) {
|
||||||
while (data.bytesLeft() >= Atom.HEADER_SIZE) {
|
int atomPosition = meta.getPosition();
|
||||||
int payloadSize = data.readInt() - Atom.HEADER_SIZE;
|
int atomSize = meta.readInt();
|
||||||
int atomType = data.readInt();
|
int atomType = meta.readInt();
|
||||||
if (atomType == Atom.TYPE_ilst) {
|
if (atomType == Atom.TYPE_ilst) {
|
||||||
ilst.reset(data.data, data.getPosition() + payloadSize);
|
meta.setPosition(atomPosition);
|
||||||
ilst.setPosition(data.getPosition());
|
return parseIlst(meta, atomPosition + atomSize);
|
||||||
Metadata metadata = parseIlst(ilst);
|
|
||||||
if (metadata != null) {
|
|
||||||
return metadata;
|
|
||||||
}
|
}
|
||||||
}
|
meta.skipBytes(atomSize - Atom.HEADER_SIZE);
|
||||||
data.skipBytes(payloadSize);
|
|
||||||
}
|
}
|
||||||
return null;
|
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<>();
|
ArrayList<Metadata.Entry> entries = new ArrayList<>();
|
||||||
while (ilst.bytesLeft() > 0) {
|
while (ilst.getPosition() < limit) {
|
||||||
int position = ilst.getPosition();
|
Metadata.Entry entry = MetadataUtil.parseIlstElement(ilst);
|
||||||
int endPosition = position + ilst.readInt();
|
if (entry != null) {
|
||||||
int type = ilst.readInt();
|
entries.add(entry);
|
||||||
parseIlstElement(ilst, type, endPosition, entries);
|
}
|
||||||
ilst.setPosition(endPosition);
|
|
||||||
}
|
}
|
||||||
return entries.isEmpty() ? null : new Metadata(entries);
|
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.
|
* 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) {
|
private static long parseMvhd(ParsableByteArray mvhd) {
|
||||||
mvhd.setPosition(Atom.HEADER_SIZE);
|
mvhd.setPosition(Atom.HEADER_SIZE);
|
||||||
|
|
||||||
int fullAtom = mvhd.readInt();
|
int fullAtom = mvhd.readInt();
|
||||||
int version = Atom.parseFullAtomVersion(fullAtom);
|
int version = Atom.parseFullAtomVersion(fullAtom);
|
||||||
|
|
||||||
mvhd.skipBytes(version == 0 ? 8 : 16);
|
mvhd.skipBytes(version == 0 ? 8 : 16);
|
||||||
|
|
||||||
return mvhd.readUnsignedInt();
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue