1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.extractor.mp4; 17 18 import androidx.annotation.Nullable; 19 import androidx.annotation.VisibleForTesting; 20 import com.google.android.exoplayer2.C; 21 import com.google.android.exoplayer2.Format; 22 import com.google.android.exoplayer2.extractor.GaplessInfoHolder; 23 import com.google.android.exoplayer2.metadata.Metadata; 24 import com.google.android.exoplayer2.metadata.id3.ApicFrame; 25 import com.google.android.exoplayer2.metadata.id3.CommentFrame; 26 import com.google.android.exoplayer2.metadata.id3.Id3Frame; 27 import com.google.android.exoplayer2.metadata.id3.InternalFrame; 28 import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; 29 import com.google.android.exoplayer2.util.Log; 30 import com.google.android.exoplayer2.util.ParsableByteArray; 31 32 /** Utilities for handling metadata in MP4. */ 33 /* package */ final class MetadataUtil { 34 35 private static final String TAG = "MetadataUtil"; 36 37 // Codes that start with the copyright character (omitted) and have equivalent ID3 frames. 38 private static final int SHORT_TYPE_NAME_1 = 0x006e616d; 39 private static final int SHORT_TYPE_NAME_2 = 0x0074726b; 40 private static final int SHORT_TYPE_COMMENT = 0x00636d74; 41 private static final int SHORT_TYPE_YEAR = 0x00646179; 42 private static final int SHORT_TYPE_ARTIST = 0x00415254; 43 private static final int SHORT_TYPE_ENCODER = 0x00746f6f; 44 private static final int SHORT_TYPE_ALBUM = 0x00616c62; 45 private static final int SHORT_TYPE_COMPOSER_1 = 0x00636f6d; 46 private static final int SHORT_TYPE_COMPOSER_2 = 0x00777274; 47 private static final int SHORT_TYPE_LYRICS = 0x006c7972; 48 private static final int SHORT_TYPE_GENRE = 0x0067656e; 49 50 // Codes that have equivalent ID3 frames. 51 private static final int TYPE_COVER_ART = 0x636f7672; 52 private static final int TYPE_GENRE = 0x676e7265; 53 private static final int TYPE_GROUPING = 0x00677270; 54 private static final int TYPE_DISK_NUMBER = 0x6469736b; 55 private static final int TYPE_TRACK_NUMBER = 0x74726b6e; 56 private static final int TYPE_TEMPO = 0x746d706f; 57 private static final int TYPE_COMPILATION = 0x6370696c; 58 private static final int TYPE_ALBUM_ARTIST = 0x61415254; 59 private static final int TYPE_SORT_TRACK_NAME = 0x736f6e6d; 60 private static final int TYPE_SORT_ALBUM = 0x736f616c; 61 private static final int TYPE_SORT_ARTIST = 0x736f6172; 62 private static final int TYPE_SORT_ALBUM_ARTIST = 0x736f6161; 63 private static final int TYPE_SORT_COMPOSER = 0x736f636f; 64 65 // Types that do not have equivalent ID3 frames. 66 private static final int TYPE_RATING = 0x72746e67; 67 private static final int TYPE_GAPLESS_ALBUM = 0x70676170; 68 private static final int TYPE_TV_SORT_SHOW = 0x736f736e; 69 private static final int TYPE_TV_SHOW = 0x74767368; 70 71 // Type for items that are intended for internal use by the player. 72 private static final int TYPE_INTERNAL = 0x2d2d2d2d; 73 74 private static final int PICTURE_TYPE_FRONT_COVER = 3; 75 76 // Standard genres. 77 @VisibleForTesting 78 /* package */ static final String[] STANDARD_GENRES = 79 new String[] { 80 // These are the official ID3v1 genres. 81 "Blues", 82 "Classic Rock", 83 "Country", 84 "Dance", 85 "Disco", 86 "Funk", 87 "Grunge", 88 "Hip-Hop", 89 "Jazz", 90 "Metal", 91 "New Age", 92 "Oldies", 93 "Other", 94 "Pop", 95 "R&B", 96 "Rap", 97 "Reggae", 98 "Rock", 99 "Techno", 100 "Industrial", 101 "Alternative", 102 "Ska", 103 "Death Metal", 104 "Pranks", 105 "Soundtrack", 106 "Euro-Techno", 107 "Ambient", 108 "Trip-Hop", 109 "Vocal", 110 "Jazz+Funk", 111 "Fusion", 112 "Trance", 113 "Classical", 114 "Instrumental", 115 "Acid", 116 "House", 117 "Game", 118 "Sound Clip", 119 "Gospel", 120 "Noise", 121 "AlternRock", 122 "Bass", 123 "Soul", 124 "Punk", 125 "Space", 126 "Meditative", 127 "Instrumental Pop", 128 "Instrumental Rock", 129 "Ethnic", 130 "Gothic", 131 "Darkwave", 132 "Techno-Industrial", 133 "Electronic", 134 "Pop-Folk", 135 "Eurodance", 136 "Dream", 137 "Southern Rock", 138 "Comedy", 139 "Cult", 140 "Gangsta", 141 "Top 40", 142 "Christian Rap", 143 "Pop/Funk", 144 "Jungle", 145 "Native American", 146 "Cabaret", 147 "New Wave", 148 "Psychadelic", 149 "Rave", 150 "Showtunes", 151 "Trailer", 152 "Lo-Fi", 153 "Tribal", 154 "Acid Punk", 155 "Acid Jazz", 156 "Polka", 157 "Retro", 158 "Musical", 159 "Rock & Roll", 160 "Hard Rock", 161 // Genres made up by the authors of Winamp (v1.91) and later added to the ID3 spec. 162 "Folk", 163 "Folk-Rock", 164 "National Folk", 165 "Swing", 166 "Fast Fusion", 167 "Bebob", 168 "Latin", 169 "Revival", 170 "Celtic", 171 "Bluegrass", 172 "Avantgarde", 173 "Gothic Rock", 174 "Progressive Rock", 175 "Psychedelic Rock", 176 "Symphonic Rock", 177 "Slow Rock", 178 "Big Band", 179 "Chorus", 180 "Easy Listening", 181 "Acoustic", 182 "Humour", 183 "Speech", 184 "Chanson", 185 "Opera", 186 "Chamber Music", 187 "Sonata", 188 "Symphony", 189 "Booty Bass", 190 "Primus", 191 "Porn Groove", 192 "Satire", 193 "Slow Jam", 194 "Club", 195 "Tango", 196 "Samba", 197 "Folklore", 198 "Ballad", 199 "Power Ballad", 200 "Rhythmic Soul", 201 "Freestyle", 202 "Duet", 203 "Punk Rock", 204 "Drum Solo", 205 "A capella", 206 "Euro-House", 207 "Dance Hall", 208 // Genres made up by the authors of Winamp (v1.91) but have not been added to the ID3 spec. 209 "Goa", 210 "Drum & Bass", 211 "Club-House", 212 "Hardcore", 213 "Terror", 214 "Indie", 215 "BritPop", 216 "Afro-Punk", 217 "Polsk Punk", 218 "Beat", 219 "Christian Gangsta Rap", 220 "Heavy Metal", 221 "Black Metal", 222 "Crossover", 223 "Contemporary Christian", 224 "Christian Rock", 225 "Merengue", 226 "Salsa", 227 "Thrash Metal", 228 "Anime", 229 "Jpop", 230 "Synthpop", 231 // Genres made up by the authors of Winamp (v5.6) but have not been added to the ID3 spec. 232 "Abstract", 233 "Art Rock", 234 "Baroque", 235 "Bhangra", 236 "Big beat", 237 "Breakbeat", 238 "Chillout", 239 "Downtempo", 240 "Dub", 241 "EBM", 242 "Eclectic", 243 "Electro", 244 "Electroclash", 245 "Emo", 246 "Experimental", 247 "Garage", 248 "Global", 249 "IDM", 250 "Illbient", 251 "Industro-Goth", 252 "Jam Band", 253 "Krautrock", 254 "Leftfield", 255 "Lounge", 256 "Math Rock", 257 "New Romantic", 258 "Nu-Breakz", 259 "Post-Punk", 260 "Post-Rock", 261 "Psytrance", 262 "Shoegaze", 263 "Space Rock", 264 "Trop Rock", 265 "World Music", 266 "Neoclassical", 267 "Audiobook", 268 "Audio theatre", 269 "Neue Deutsche Welle", 270 "Podcast", 271 "Indie-Rock", 272 "G-Funk", 273 "Dubstep", 274 "Garage Rock", 275 "Psybient" 276 }; 277 278 private static final String LANGUAGE_UNDEFINED = "und"; 279 280 private static final int TYPE_TOP_BYTE_COPYRIGHT = 0xA9; 281 private static final int TYPE_TOP_BYTE_REPLACEMENT = 0xFD; // Truncated value of \uFFFD. 282 283 private static final String MDTA_KEY_ANDROID_CAPTURE_FPS = "com.android.capture.fps"; 284 MetadataUtil()285 private MetadataUtil() {} 286 287 /** Updates a {@link Format.Builder} to include metadata from the provided sources. */ setFormatMetadata( int trackType, @Nullable Metadata udtaMetadata, @Nullable Metadata mdtaMetadata, GaplessInfoHolder gaplessInfoHolder, Format.Builder formatBuilder)288 public static void setFormatMetadata( 289 int trackType, 290 @Nullable Metadata udtaMetadata, 291 @Nullable Metadata mdtaMetadata, 292 GaplessInfoHolder gaplessInfoHolder, 293 Format.Builder formatBuilder) { 294 if (trackType == C.TRACK_TYPE_AUDIO) { 295 if (gaplessInfoHolder.hasGaplessInfo()) { 296 formatBuilder 297 .setEncoderDelay(gaplessInfoHolder.encoderDelay) 298 .setEncoderPadding(gaplessInfoHolder.encoderPadding); 299 } 300 // We assume all udta metadata is associated with the audio track. 301 if (udtaMetadata != null) { 302 formatBuilder.setMetadata(udtaMetadata); 303 } 304 } else if (trackType == C.TRACK_TYPE_VIDEO && mdtaMetadata != null) { 305 // Populate only metadata keys that are known to be specific to video. 306 for (int i = 0; i < mdtaMetadata.length(); i++) { 307 Metadata.Entry entry = mdtaMetadata.get(i); 308 if (entry instanceof MdtaMetadataEntry) { 309 MdtaMetadataEntry mdtaMetadataEntry = (MdtaMetadataEntry) entry; 310 if (MDTA_KEY_ANDROID_CAPTURE_FPS.equals(mdtaMetadataEntry.key)) { 311 formatBuilder.setMetadata(new Metadata(mdtaMetadataEntry)); 312 } 313 } 314 } 315 } 316 } 317 318 /** 319 * Parses a single userdata ilst element from a {@link ParsableByteArray}. The element is read 320 * starting from the current position of the {@link ParsableByteArray}, and the position is 321 * advanced by the size of the element. The position is advanced even if the element's type is 322 * unrecognized. 323 * 324 * @param ilst Holds the data to be parsed. 325 * @return The parsed element, or null if the element's type was not recognized. 326 */ 327 @Nullable parseIlstElement(ParsableByteArray ilst)328 public static Metadata.Entry parseIlstElement(ParsableByteArray ilst) { 329 int position = ilst.getPosition(); 330 int endPosition = position + ilst.readInt(); 331 int type = ilst.readInt(); 332 int typeTopByte = (type >> 24) & 0xFF; 333 try { 334 if (typeTopByte == TYPE_TOP_BYTE_COPYRIGHT || typeTopByte == TYPE_TOP_BYTE_REPLACEMENT) { 335 int shortType = type & 0x00FFFFFF; 336 if (shortType == SHORT_TYPE_COMMENT) { 337 return parseCommentAttribute(type, ilst); 338 } else if (shortType == SHORT_TYPE_NAME_1 || shortType == SHORT_TYPE_NAME_2) { 339 return parseTextAttribute(type, "TIT2", ilst); 340 } else if (shortType == SHORT_TYPE_COMPOSER_1 || shortType == SHORT_TYPE_COMPOSER_2) { 341 return parseTextAttribute(type, "TCOM", ilst); 342 } else if (shortType == SHORT_TYPE_YEAR) { 343 return parseTextAttribute(type, "TDRC", ilst); 344 } else if (shortType == SHORT_TYPE_ARTIST) { 345 return parseTextAttribute(type, "TPE1", ilst); 346 } else if (shortType == SHORT_TYPE_ENCODER) { 347 return parseTextAttribute(type, "TSSE", ilst); 348 } else if (shortType == SHORT_TYPE_ALBUM) { 349 return parseTextAttribute(type, "TALB", ilst); 350 } else if (shortType == SHORT_TYPE_LYRICS) { 351 return parseTextAttribute(type, "USLT", ilst); 352 } else if (shortType == SHORT_TYPE_GENRE) { 353 return parseTextAttribute(type, "TCON", ilst); 354 } else if (shortType == TYPE_GROUPING) { 355 return parseTextAttribute(type, "TIT1", ilst); 356 } 357 } else if (type == TYPE_GENRE) { 358 return parseStandardGenreAttribute(ilst); 359 } else if (type == TYPE_DISK_NUMBER) { 360 return parseIndexAndCountAttribute(type, "TPOS", ilst); 361 } else if (type == TYPE_TRACK_NUMBER) { 362 return parseIndexAndCountAttribute(type, "TRCK", ilst); 363 } else if (type == TYPE_TEMPO) { 364 return parseUint8Attribute(type, "TBPM", ilst, true, false); 365 } else if (type == TYPE_COMPILATION) { 366 return parseUint8Attribute(type, "TCMP", ilst, true, true); 367 } else if (type == TYPE_COVER_ART) { 368 return parseCoverArt(ilst); 369 } else if (type == TYPE_ALBUM_ARTIST) { 370 return parseTextAttribute(type, "TPE2", ilst); 371 } else if (type == TYPE_SORT_TRACK_NAME) { 372 return parseTextAttribute(type, "TSOT", ilst); 373 } else if (type == TYPE_SORT_ALBUM) { 374 return parseTextAttribute(type, "TSO2", ilst); 375 } else if (type == TYPE_SORT_ARTIST) { 376 return parseTextAttribute(type, "TSOA", ilst); 377 } else if (type == TYPE_SORT_ALBUM_ARTIST) { 378 return parseTextAttribute(type, "TSOP", ilst); 379 } else if (type == TYPE_SORT_COMPOSER) { 380 return parseTextAttribute(type, "TSOC", ilst); 381 } else if (type == TYPE_RATING) { 382 return parseUint8Attribute(type, "ITUNESADVISORY", ilst, false, false); 383 } else if (type == TYPE_GAPLESS_ALBUM) { 384 return parseUint8Attribute(type, "ITUNESGAPLESS", ilst, false, true); 385 } else if (type == TYPE_TV_SORT_SHOW) { 386 return parseTextAttribute(type, "TVSHOWSORT", ilst); 387 } else if (type == TYPE_TV_SHOW) { 388 return parseTextAttribute(type, "TVSHOW", ilst); 389 } else if (type == TYPE_INTERNAL) { 390 return parseInternalAttribute(ilst, endPosition); 391 } 392 Log.d(TAG, "Skipped unknown metadata entry: " + Atom.getAtomTypeString(type)); 393 return null; 394 } finally { 395 ilst.setPosition(endPosition); 396 } 397 } 398 399 /** 400 * Parses an 'mdta' metadata entry starting at the current position in an ilst box. 401 * 402 * @param ilst The ilst box. 403 * @param endPosition The end position of the entry in the ilst box. 404 * @param key The mdta metadata entry key for the entry. 405 * @return The parsed element, or null if the entry wasn't recognized. 406 */ 407 @Nullable parseMdtaMetadataEntryFromIlst( ParsableByteArray ilst, int endPosition, String key)408 public static MdtaMetadataEntry parseMdtaMetadataEntryFromIlst( 409 ParsableByteArray ilst, int endPosition, String key) { 410 int atomPosition; 411 while ((atomPosition = ilst.getPosition()) < endPosition) { 412 int atomSize = ilst.readInt(); 413 int atomType = ilst.readInt(); 414 if (atomType == Atom.TYPE_data) { 415 int typeIndicator = ilst.readInt(); 416 int localeIndicator = ilst.readInt(); 417 int dataSize = atomSize - 16; 418 byte[] value = new byte[dataSize]; 419 ilst.readBytes(value, 0, dataSize); 420 return new MdtaMetadataEntry(key, value, localeIndicator, typeIndicator); 421 } 422 ilst.setPosition(atomPosition + atomSize); 423 } 424 return null; 425 } 426 427 @Nullable parseTextAttribute( int type, String id, ParsableByteArray data)428 private static TextInformationFrame parseTextAttribute( 429 int type, String id, ParsableByteArray data) { 430 int atomSize = data.readInt(); 431 int atomType = data.readInt(); 432 if (atomType == Atom.TYPE_data) { 433 data.skipBytes(8); // version (1), flags (3), empty (4) 434 String value = data.readNullTerminatedString(atomSize - 16); 435 return new TextInformationFrame(id, /* description= */ null, value); 436 } 437 Log.w(TAG, "Failed to parse text attribute: " + Atom.getAtomTypeString(type)); 438 return null; 439 } 440 441 @Nullable parseCommentAttribute(int type, ParsableByteArray data)442 private static CommentFrame parseCommentAttribute(int type, ParsableByteArray data) { 443 int atomSize = data.readInt(); 444 int atomType = data.readInt(); 445 if (atomType == Atom.TYPE_data) { 446 data.skipBytes(8); // version (1), flags (3), empty (4) 447 String value = data.readNullTerminatedString(atomSize - 16); 448 return new CommentFrame(LANGUAGE_UNDEFINED, value, value); 449 } 450 Log.w(TAG, "Failed to parse comment attribute: " + Atom.getAtomTypeString(type)); 451 return null; 452 } 453 454 @Nullable parseUint8Attribute( int type, String id, ParsableByteArray data, boolean isTextInformationFrame, boolean isBoolean)455 private static Id3Frame parseUint8Attribute( 456 int type, 457 String id, 458 ParsableByteArray data, 459 boolean isTextInformationFrame, 460 boolean isBoolean) { 461 int value = parseUint8AttributeValue(data); 462 if (isBoolean) { 463 value = Math.min(1, value); 464 } 465 if (value >= 0) { 466 return isTextInformationFrame 467 ? new TextInformationFrame(id, /* description= */ null, Integer.toString(value)) 468 : new CommentFrame(LANGUAGE_UNDEFINED, id, Integer.toString(value)); 469 } 470 Log.w(TAG, "Failed to parse uint8 attribute: " + Atom.getAtomTypeString(type)); 471 return null; 472 } 473 474 @Nullable parseIndexAndCountAttribute( int type, String attributeName, ParsableByteArray data)475 private static TextInformationFrame parseIndexAndCountAttribute( 476 int type, String attributeName, ParsableByteArray data) { 477 int atomSize = data.readInt(); 478 int atomType = data.readInt(); 479 if (atomType == Atom.TYPE_data && atomSize >= 22) { 480 data.skipBytes(10); // version (1), flags (3), empty (4), empty (2) 481 int index = data.readUnsignedShort(); 482 if (index > 0) { 483 String value = "" + index; 484 int count = data.readUnsignedShort(); 485 if (count > 0) { 486 value += "/" + count; 487 } 488 return new TextInformationFrame(attributeName, /* description= */ null, value); 489 } 490 } 491 Log.w(TAG, "Failed to parse index/count attribute: " + Atom.getAtomTypeString(type)); 492 return null; 493 } 494 495 @Nullable parseStandardGenreAttribute(ParsableByteArray data)496 private static TextInformationFrame parseStandardGenreAttribute(ParsableByteArray data) { 497 int genreCode = parseUint8AttributeValue(data); 498 @Nullable 499 String genreString = 500 (0 < genreCode && genreCode <= STANDARD_GENRES.length) 501 ? STANDARD_GENRES[genreCode - 1] 502 : null; 503 if (genreString != null) { 504 return new TextInformationFrame("TCON", /* description= */ null, genreString); 505 } 506 Log.w(TAG, "Failed to parse standard genre code"); 507 return null; 508 } 509 510 @Nullable parseCoverArt(ParsableByteArray data)511 private static ApicFrame parseCoverArt(ParsableByteArray data) { 512 int atomSize = data.readInt(); 513 int atomType = data.readInt(); 514 if (atomType == Atom.TYPE_data) { 515 int fullVersionInt = data.readInt(); 516 int flags = Atom.parseFullAtomFlags(fullVersionInt); 517 @Nullable String mimeType = flags == 13 ? "image/jpeg" : flags == 14 ? "image/png" : null; 518 if (mimeType == null) { 519 Log.w(TAG, "Unrecognized cover art flags: " + flags); 520 return null; 521 } 522 data.skipBytes(4); // empty (4) 523 byte[] pictureData = new byte[atomSize - 16]; 524 data.readBytes(pictureData, 0, pictureData.length); 525 return new ApicFrame( 526 mimeType, 527 /* description= */ null, 528 /* pictureType= */ PICTURE_TYPE_FRONT_COVER, 529 pictureData); 530 } 531 Log.w(TAG, "Failed to parse cover art attribute"); 532 return null; 533 } 534 535 @Nullable parseInternalAttribute(ParsableByteArray data, int endPosition)536 private static Id3Frame parseInternalAttribute(ParsableByteArray data, int endPosition) { 537 @Nullable String domain = null; 538 @Nullable String name = null; 539 int dataAtomPosition = -1; 540 int dataAtomSize = -1; 541 while (data.getPosition() < endPosition) { 542 int atomPosition = data.getPosition(); 543 int atomSize = data.readInt(); 544 int atomType = data.readInt(); 545 data.skipBytes(4); // version (1), flags (3) 546 if (atomType == Atom.TYPE_mean) { 547 domain = data.readNullTerminatedString(atomSize - 12); 548 } else if (atomType == Atom.TYPE_name) { 549 name = data.readNullTerminatedString(atomSize - 12); 550 } else { 551 if (atomType == Atom.TYPE_data) { 552 dataAtomPosition = atomPosition; 553 dataAtomSize = atomSize; 554 } 555 data.skipBytes(atomSize - 12); 556 } 557 } 558 if (domain == null || name == null || dataAtomPosition == -1) { 559 return null; 560 } 561 data.setPosition(dataAtomPosition); 562 data.skipBytes(16); // size (4), type (4), version (1), flags (3), empty (4) 563 String value = data.readNullTerminatedString(dataAtomSize - 16); 564 return new InternalFrame(domain, name, value); 565 } 566 parseUint8AttributeValue(ParsableByteArray data)567 private static int parseUint8AttributeValue(ParsableByteArray data) { 568 data.skipBytes(4); // atomSize 569 int atomType = data.readInt(); 570 if (atomType == Atom.TYPE_data) { 571 data.skipBytes(8); // version (1), flags (3), empty (4) 572 return data.readUnsignedByte(); 573 } 574 Log.w(TAG, "Failed to parse uint8 attribute value"); 575 return -1; 576 } 577 578 } 579