TextInformationFrame.java
/*
* 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 androidx.media3.extractor.metadata.id3;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.InlineMe;
import java.util.ArrayList;
import java.util.List;
/** Text information ID3 frame. */
@UnstableApi
public final class TextInformationFrame extends Id3Frame {
@Nullable public final String description;
/**
* @deprecated Use the first element of {@link #values} instead.
*/
@Deprecated public final String value;
/** The text values of this frame. Will always have at least one element. */
public final ImmutableList<String> values;
public TextInformationFrame(String id, @Nullable String description, List<String> values) {
super(id);
checkArgument(!values.isEmpty());
this.description = description;
this.values = ImmutableList.copyOf(values);
this.value = this.values.get(0);
}
/**
* @deprecated Use {@code TextInformationFrame(String id, String description, String[] values}
* instead
*/
@Deprecated
@InlineMe(
replacement = "this(id, description, ImmutableList.of(value))",
imports = "com.google.common.collect.ImmutableList")
public TextInformationFrame(String id, @Nullable String description, String value) {
this(id, description, ImmutableList.of(value));
}
private TextInformationFrame(Parcel in) {
this(
checkNotNull(in.readString()),
in.readString(),
ImmutableList.copyOf(checkNotNull(in.createStringArray())));
}
/**
* Uses the first element in {@link #values} to set the relevant field in {@link MediaMetadata}
* (as determined by {@link #id}).
*/
@Override
public void populateMediaMetadata(MediaMetadata.Builder builder) {
switch (id) {
case "TT2":
case "TIT2":
builder.setTitle(values.get(0));
break;
case "TP1":
case "TPE1":
builder.setArtist(values.get(0));
break;
case "TP2":
case "TPE2":
builder.setAlbumArtist(values.get(0));
break;
case "TAL":
case "TALB":
builder.setAlbumTitle(values.get(0));
break;
case "TRK":
case "TRCK":
String[] trackNumbers = Util.split(values.get(0), "/");
try {
int trackNumber = Integer.parseInt(trackNumbers[0]);
@Nullable
Integer totalTrackCount =
trackNumbers.length > 1 ? Integer.parseInt(trackNumbers[1]) : null;
builder.setTrackNumber(trackNumber).setTotalTrackCount(totalTrackCount);
} catch (NumberFormatException e) {
// Do nothing, invalid input.
}
break;
case "TYE":
case "TYER":
try {
builder.setRecordingYear(Integer.parseInt(values.get(0)));
} catch (NumberFormatException e) {
// Do nothing, invalid input.
}
break;
case "TDA":
case "TDAT":
try {
String date = values.get(0);
int month = Integer.parseInt(date.substring(2, 4));
int day = Integer.parseInt(date.substring(0, 2));
builder.setRecordingMonth(month).setRecordingDay(day);
} catch (NumberFormatException | StringIndexOutOfBoundsException e) {
// Do nothing, invalid input.
}
break;
case "TDRC":
List<Integer> recordingDate = parseId3v2point4TimestampFrameForDate(values.get(0));
switch (recordingDate.size()) {
case 3:
builder.setRecordingDay(recordingDate.get(2));
// fall through
case 2:
builder.setRecordingMonth(recordingDate.get(1));
// fall through
case 1:
builder.setRecordingYear(recordingDate.get(0));
// fall through
break;
default:
// Do nothing.
break;
}
break;
case "TDRL":
List<Integer> releaseDate = parseId3v2point4TimestampFrameForDate(values.get(0));
switch (releaseDate.size()) {
case 3:
builder.setReleaseDay(releaseDate.get(2));
// fall through
case 2:
builder.setReleaseMonth(releaseDate.get(1));
// fall through
case 1:
builder.setReleaseYear(releaseDate.get(0));
// fall through
break;
default:
// Do nothing.
break;
}
break;
case "TCM":
case "TCOM":
builder.setComposer(values.get(0));
break;
case "TP3":
case "TPE3":
builder.setConductor(values.get(0));
break;
case "TXT":
case "TEXT":
builder.setWriter(values.get(0));
break;
default:
break;
}
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
TextInformationFrame other = (TextInformationFrame) obj;
return Util.areEqual(id, other.id)
&& Util.areEqual(description, other.description)
&& values.equals(other.values);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + id.hashCode();
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + values.hashCode();
return result;
}
@Override
public String toString() {
return id + ": description=" + description + ": values=" + values;
}
// Parcelable implementation.
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(id);
dest.writeString(description);
dest.writeStringArray(values.toArray(new String[0]));
}
public static final Parcelable.Creator<TextInformationFrame> CREATOR =
new Parcelable.Creator<TextInformationFrame>() {
@Override
public TextInformationFrame createFromParcel(Parcel in) {
return new TextInformationFrame(in);
}
@Override
public TextInformationFrame[] newArray(int size) {
return new TextInformationFrame[size];
}
};
// Private methods
private static List<Integer> parseId3v2point4TimestampFrameForDate(String value) {
// Timestamp string format is ISO-8601, can be `yyyy-MM-ddTHH:mm:ss`, or reduced precision
// at each point, for example `yyyy-MM` or `yyyy-MM-ddTHH:mm`.
List<Integer> dates = new ArrayList<>();
try {
if (value.length() >= 10) {
dates.add(Integer.parseInt(value.substring(0, 4)));
dates.add(Integer.parseInt(value.substring(5, 7)));
dates.add(Integer.parseInt(value.substring(8, 10)));
} else if (value.length() >= 7) {
dates.add(Integer.parseInt(value.substring(0, 4)));
dates.add(Integer.parseInt(value.substring(5, 7)));
} else if (value.length() >= 4) {
dates.add(Integer.parseInt(value.substring(0, 4)));
}
} catch (NumberFormatException e) {
// Invalid output, return.
return new ArrayList<>();
}
return dates;
}
}