blob: d9c38af150853cb0a9611d470772127947393a08 [file] [log] [blame]
/*
* Copyright (C) 2010 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 android.media.videoeditor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import android.graphics.Bitmap;
/**
* This abstract class describes the base class for any MediaItem. Objects are
* defined with a file path as a source data.
* {@hide}
s */
public abstract class MediaItem {
// A constant which can be used to specify the end of the file (instead of
// providing the actual duration of the media item).
public final static int END_OF_FILE = -1;
// Rendering modes
/**
* When using the RENDERING_MODE_BLACK_BORDER rendering mode video frames
* are resized by preserving the aspect ratio until the movie matches one of
* the dimensions of the output movie. The areas outside the resized video
* clip are rendered black.
*/
public static final int RENDERING_MODE_BLACK_BORDER = 0;
/**
* When using the RENDERING_MODE_STRETCH rendering mode video frames are
* stretched horizontally or vertically to match the current aspect ratio of
* the movie.
*/
public static final int RENDERING_MODE_STRETCH = 1;
// The unique id of the MediaItem
private final String mUniqueId;
// The name of the file associated with the MediaItem
protected final String mFilename;
// List of effects
private final List<Effect> mEffects;
// List of overlays
private final List<Overlay> mOverlays;
// The rendering mode
private int mRenderingMode;
// Beginning and end transitions
protected Transition mBeginTransition;
protected Transition mEndTransition;
/**
* Constructor
*
* @param editor The video editor reference
* @param mediaItemId The MediaItem id
* @param filename name of the media file.
* @param renderingMode The rendering mode
*
* @throws IOException if file is not found
* @throws IllegalArgumentException if a capability such as file format is not
* supported the exception object contains the unsupported
* capability
*/
protected MediaItem(VideoEditor editor, String mediaItemId, String filename,
int renderingMode) throws IOException {
mUniqueId = mediaItemId;
mFilename = filename;
mRenderingMode = renderingMode;
mEffects = new ArrayList<Effect>();
mOverlays = new ArrayList<Overlay>();
mBeginTransition = null;
mEndTransition = null;
}
/**
* @return The id of the media item
*/
public String getId() {
return mUniqueId;
}
/**
* @return The media source file name
*/
public String getFilename() {
return mFilename;
}
/**
* If aspect ratio of the MediaItem is different from the aspect ratio of
* the editor then this API controls the rendering mode.
*
* @param renderingMode rendering mode. It is one of:
* {@link #RENDERING_MODE_BLACK_BORDER},
* {@link #RENDERING_MODE_STRETCH}
*/
public void setRenderingMode(int renderingMode) {
mRenderingMode = renderingMode;
if (mBeginTransition != null) {
mBeginTransition.invalidate();
}
if (mEndTransition != null) {
mEndTransition.invalidate();
}
}
/**
* @return The rendering mode
*/
public int getRenderingMode() {
return mRenderingMode;
}
/**
* @param transition The beginning transition
*/
void setBeginTransition(Transition transition) {
mBeginTransition = transition;
}
/**
* @return The begin transition
*/
public Transition getBeginTransition() {
return mBeginTransition;
}
/**
* @param transition The end transition
*/
void setEndTransition(Transition transition) {
mEndTransition = transition;
}
/**
* @return The end transition
*/
public Transition getEndTransition() {
return mEndTransition;
}
/**
* @return The timeline duration. This is the actual duration in the
* timeline (trimmed duration)
*/
public abstract long getTimelineDuration();
/**
* @return The source file type
*/
public abstract int getFileType();
/**
* @return Get the native width of the media item
*/
public abstract int getWidth();
/**
* @return Get the native height of the media item
*/
public abstract int getHeight();
/**
* Get aspect ratio of the source media item.
*
* @return the aspect ratio as described in MediaProperties.
* MediaProperties.ASPECT_RATIO_UNDEFINED if aspect ratio is not
* supported as in MediaProperties
*/
public abstract int getAspectRatio();
/**
* Add the specified effect to this media item.
*
* Note that certain types of effects cannot be applied to video and to
* image media items. For example in certain implementation a Ken Burns
* implementation cannot be applied to video media item.
*
* This method invalidates transition video clips if the
* effect overlaps with the beginning and/or the end transition.
*
* @param effect The effect to apply
* @throws IllegalStateException if a preview or an export is in progress
* @throws IllegalArgumentException if the effect start and/or duration are
* invalid or if the effect cannot be applied to this type of media
* item or if the effect id is not unique across all the Effects
* added.
*/
public void addEffect(Effect effect) {
if (effect.getMediaItem() != this) {
throw new IllegalArgumentException("Media item mismatch");
}
if (mEffects.contains(effect)) {
throw new IllegalArgumentException("Effect already exists: " + effect.getId());
}
if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()) {
throw new IllegalArgumentException(
"Effect start time + effect duration > media clip duration");
}
mEffects.add(effect);
invalidateTransitions(effect);
}
/**
* Remove the effect with the specified id.
*
* This method invalidates a transition video clip if the effect overlaps
* with a transition.
*
* @param effectId The id of the effect to be removed
*
* @return The effect that was removed
* @throws IllegalStateException if a preview or an export is in progress
*/
public Effect removeEffect(String effectId) {
for (Effect effect : mEffects) {
if (effect.getId().equals(effectId)) {
mEffects.remove(effect);
invalidateTransitions(effect);
return effect;
}
}
return null;
}
/**
* Find the effect with the specified id
*
* @param effectId The effect id
*
* @return The effect with the specified id (null if it does not exist)
*/
public Effect getEffect(String effectId) {
for (Effect effect : mEffects) {
if (effect.getId().equals(effectId)) {
return effect;
}
}
return null;
}
/**
* Get the list of effects.
*
* @return the effects list. If no effects exist an empty list will be returned.
*/
public List<Effect> getAllEffects() {
return mEffects;
}
/**
* Add an overlay to the storyboard. This method invalidates a transition
* video clip if the overlay overlaps with a transition.
*
* @param overlay The overlay to add
* @throws IllegalStateException if a preview or an export is in progress or
* if the overlay id is not unique across all the overlays
* added or if the bitmap is not specified or if the dimensions of
* the bitmap do not match the dimensions of the media item
*/
public void addOverlay(Overlay overlay) {
if (overlay.getMediaItem() != this) {
throw new IllegalArgumentException("Media item mismatch");
}
if (mOverlays.contains(overlay)) {
throw new IllegalArgumentException("Overlay already exists: " + overlay.getId());
}
if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()) {
throw new IllegalArgumentException(
"Overlay start time + overlay duration > media clip duration");
}
if (overlay instanceof OverlayFrame) {
final OverlayFrame frame = (OverlayFrame)overlay;
final Bitmap bitmap = frame.getBitmap();
if (bitmap == null) {
throw new IllegalArgumentException("Overlay bitmap not specified");
}
final int scaledWidth, scaledHeight;
if (this instanceof MediaVideoItem) {
scaledWidth = getWidth();
scaledHeight = getHeight();
} else {
scaledWidth = ((MediaImageItem)this).getScaledWidth();
scaledHeight = ((MediaImageItem)this).getScaledHeight();
}
// The dimensions of the overlay bitmap must be the same as the
// media item dimensions
if (bitmap.getWidth() != scaledWidth || bitmap.getHeight() != scaledHeight) {
throw new IllegalArgumentException(
"Bitmap dimensions must match media item dimensions");
}
} else {
throw new IllegalArgumentException("Overlay not supported");
}
mOverlays.add(overlay);
invalidateTransitions(overlay);
}
/**
* Remove the overlay with the specified id.
*
* This method invalidates a transition video clip if the overlay overlaps
* with a transition.
*
* @param overlayId The id of the overlay to be removed
*
* @return The overlay that was removed
* @throws IllegalStateException if a preview or an export is in progress
*/
public Overlay removeOverlay(String overlayId) {
for (Overlay overlay : mOverlays) {
if (overlay.getId().equals(overlayId)) {
mOverlays.remove(overlay);
if (overlay instanceof OverlayFrame) {
((OverlayFrame)overlay).invalidate();
}
invalidateTransitions(overlay);
return overlay;
}
}
return null;
}
/**
* Find the overlay with the specified id
*
* @param overlayId The overlay id
*
* @return The overlay with the specified id (null if it does not exist)
*/
public Overlay getOverlay(String overlayId) {
for (Overlay overlay : mOverlays) {
if (overlay.getId().equals(overlayId)) {
return overlay;
}
}
return null;
}
/**
* Get the list of overlays associated with this media item
*
* Note that if any overlay source files are not accessible anymore,
* this method will still provide the full list of overlays.
*
* @return The list of overlays. If no overlays exist an empty list will
* be returned.
*/
public List<Overlay> getAllOverlays() {
return mOverlays;
}
/**
* Create a thumbnail at specified time in a video stream in Bitmap format
*
* @param width width of the thumbnail in pixels
* @param height height of the thumbnail in pixels
* @param timeMs The time in the source video file at which the thumbnail is
* requested (even if trimmed).
*
* @return The thumbnail as a Bitmap.
*
* @throws IOException if a file error occurs
* @throws IllegalArgumentException if time is out of video duration
*/
public abstract Bitmap getThumbnail(int width, int height, long timeMs) throws IOException;
/**
* Get the array of Bitmap thumbnails between start and end.
*
* @param width width of the thumbnail in pixels
* @param height height of the thumbnail in pixels
* @param startMs The start of time range in milliseconds
* @param endMs The end of the time range in milliseconds
* @param thumbnailCount The thumbnail count
*
* @return The array of Bitmaps
*
* @throws IOException if a file error occurs
*/
public abstract Bitmap[] getThumbnailList(int width, int height, long startMs, long endMs,
int thumbnailCount) throws IOException;
/*
* {@inheritDoc}
*/
@Override
public boolean equals(Object object) {
if (!(object instanceof MediaItem)) {
return false;
}
return mUniqueId.equals(((MediaItem)object).mUniqueId);
}
/*
* {@inheritDoc}
*/
@Override
public int hashCode() {
return mUniqueId.hashCode();
}
/**
* Invalidate the start and end transitions if necessary
*
* @param effect The effect that was added or removed
*/
void invalidateTransitions(Effect effect) {
// Check if the effect overlaps with the beginning and end transitions
if (mBeginTransition != null) {
if (effect.getStartTime() < mBeginTransition.getDuration()) {
mBeginTransition.invalidate();
}
}
if (mEndTransition != null) {
if (effect.getStartTime() + effect.getDuration() > getTimelineDuration()
- mEndTransition.getDuration()) {
mEndTransition.invalidate();
}
}
}
/**
* Invalidate the start and end transitions if necessary
*
* @param overlay The effect that was added or removed
*/
void invalidateTransitions(Overlay overlay) {
// Check if the overlay overlaps with the beginning and end transitions
if (mBeginTransition != null) {
if (overlay.getStartTime() < mBeginTransition.getDuration()) {
mBeginTransition.invalidate();
}
}
if (mEndTransition != null) {
if (overlay.getStartTime() + overlay.getDuration() > getTimelineDuration()
- mEndTransition.getDuration()) {
mEndTransition.invalidate();
}
}
}
/**
* Adjust the duration of effects, overlays and transitions.
* This method will be called after a media item duration is changed.
*/
protected void adjustElementsDuration() {
// Check if the duration of transitions need to be adjusted
if (mBeginTransition != null) {
final long maxDurationMs = mBeginTransition.getMaximumDuration();
if (mBeginTransition.getDuration() > maxDurationMs) {
mBeginTransition.setDuration(maxDurationMs);
}
}
if (mEndTransition != null) {
final long maxDurationMs = mEndTransition.getMaximumDuration();
if (mEndTransition.getDuration() > maxDurationMs) {
mEndTransition.setDuration(maxDurationMs);
}
}
final List<Overlay> overlays = getAllOverlays();
for (Overlay overlay : overlays) {
// Adjust the start time if necessary
final long overlayStartTimeMs;
if (overlay.getStartTime() > getTimelineDuration()) {
overlayStartTimeMs = 0;
} else {
overlayStartTimeMs = overlay.getStartTime();
}
// Adjust the duration if necessary
final long overlayDurationMs;
if (overlayStartTimeMs + overlay.getDuration() > getTimelineDuration()) {
overlayDurationMs = getTimelineDuration() - overlayStartTimeMs;
} else {
overlayDurationMs = overlay.getDuration();
}
if (overlayStartTimeMs != overlay.getStartTime() ||
overlayDurationMs != overlay.getDuration()) {
overlay.setStartTimeAndDuration(overlayStartTimeMs, overlayDurationMs);
}
}
final List<Effect> effects = getAllEffects();
for (Effect effect : effects) {
// Adjust the start time if necessary
final long effectStartTimeMs;
if (effect.getStartTime() > getTimelineDuration()) {
effectStartTimeMs = 0;
} else {
effectStartTimeMs = effect.getStartTime();
}
// Adjust the duration if necessary
final long effectDurationMs;
if (effectStartTimeMs + effect.getDuration() > getTimelineDuration()) {
effectDurationMs = getTimelineDuration() - effectStartTimeMs;
} else {
effectDurationMs = effect.getDuration();
}
if (effectStartTimeMs != effect.getStartTime() ||
effectDurationMs != effect.getDuration()) {
effect.setStartTimeAndDuration(effectStartTimeMs, effectDurationMs);
}
}
}
}