blob: 3117dfa5f49f5ccbeecded763a46645227a3aef0 [file] [log] [blame]
/*
* Copyright 2020 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.jpeg;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.metadata.mp4.MotionPhotoMetadata;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.List;
/** Describes the layout and metadata of a motion photo file. */
/* package */ final class MotionPhotoDescription {
/** Describes a media item in the motion photo. */
public static final class ContainerItem {
/** The MIME type of the media item. */
public final String mime;
/** The application-specific meaning of the media item. */
public final String semantic;
/**
* The positive integer length in bytes of the media item, or 0 for primary media items and
* secondary media items that share their resource with the preceding media item.
*/
public final long length;
/**
* The number of bytes of additional padding between the end of the primary media item and the
* start of the next media item. 0 for secondary media items.
*/
public final long padding;
public ContainerItem(String mime, String semantic, long length, long padding) {
this.mime = mime;
this.semantic = semantic;
this.length = length;
this.padding = padding;
}
}
/**
* The presentation timestamp of the primary media item, in microseconds, or {@link C#TIME_UNSET}
* if unknown.
*/
public final long photoPresentationTimestampUs;
/**
* The media items represented by the motion photo file, in order. The primary media item is
* listed first, followed by any secondary media items.
*/
public final List<ContainerItem> items;
public MotionPhotoDescription(long photoPresentationTimestampUs, List<ContainerItem> items) {
this.photoPresentationTimestampUs = photoPresentationTimestampUs;
this.items = items;
}
/**
* Returns the {@link MotionPhotoMetadata} for the motion photo represented by this instance, or
* {@code null} if there wasn't enough information to derive the metadata.
*
* @param motionPhotoLength The length of the motion photo file, in bytes.
* @return The motion photo metadata, or {@code null}.
*/
@Nullable
public MotionPhotoMetadata getMotionPhotoMetadata(long motionPhotoLength) {
if (items.size() < 2) {
// We need a primary item (photo) and at least one secondary item (video).
return null;
}
// Iterate backwards through the items to find the earlier video in the list. If we find a video
// item with length zero, we need to keep scanning backwards to find the preceding item with
// non-zero length, which is the item that contains the video data.
long photoStartPosition = C.POSITION_UNSET;
long photoLength = C.LENGTH_UNSET;
long mp4StartPosition = C.POSITION_UNSET;
long mp4Length = C.LENGTH_UNSET;
boolean itemContainsMp4 = false;
long itemStartPosition = motionPhotoLength;
long itemEndPosition = motionPhotoLength;
for (int i = items.size() - 1; i >= 0; i--) {
MotionPhotoDescription.ContainerItem item = items.get(i);
itemContainsMp4 |= MimeTypes.VIDEO_MP4.equals(item.mime);
itemEndPosition = itemStartPosition;
if (i == 0) {
// Padding is only applied for the primary item.
itemStartPosition = 0;
itemEndPosition -= item.padding;
} else {
itemStartPosition -= item.length;
}
if (itemContainsMp4 && itemStartPosition != itemEndPosition) {
mp4StartPosition = itemStartPosition;
mp4Length = itemEndPosition - itemStartPosition;
// Reset in case there's another video earlier in the list.
itemContainsMp4 = false;
}
if (i == 0) {
photoStartPosition = itemStartPosition;
photoLength = itemEndPosition;
}
}
if (mp4StartPosition == C.POSITION_UNSET
|| mp4Length == C.LENGTH_UNSET
|| photoStartPosition == C.POSITION_UNSET
|| photoLength == C.LENGTH_UNSET) {
return null;
}
return new MotionPhotoMetadata(
photoStartPosition, photoLength, photoPresentationTimestampUs, mp4StartPosition, mp4Length);
}
}