Merge "1. Added OverlayFrame constuctor 2. Invalidate transitions when trimming video 3. Remove image file when removing an OverlayFrame 4. Bug fixes in the VideoEditor implementation"

This commit is contained in:
Gil Dobjanschi
2010-09-23 13:48:33 -07:00
committed by Android (Google) Code Review
6 changed files with 231 additions and 113 deletions

View File

@@ -84,7 +84,7 @@ public abstract class Effect {
* Set start time of the effect. If a preview or export is in progress, then
* this change is effective for next preview or export session.
*
* @param startTimeMs The start time of the effect relative to the begining
* @param startTimeMs The start time of the effect relative to the beginning
* of the media item in milliseconds
*/
public void setStartTime(long startTimeMs) {

View File

@@ -64,8 +64,8 @@ public abstract class MediaItem {
private int mRenderingMode;
// Beginning and end transitions
private Transition mBeginTransition;
private Transition mEndTransition;
protected Transition mBeginTransition;
protected Transition mEndTransition;
/**
* Constructor
@@ -113,6 +113,13 @@ public abstract class MediaItem {
*/
public void setRenderingMode(int renderingMode) {
mRenderingMode = renderingMode;
if (mBeginTransition != null) {
mBeginTransition.invalidate();
}
if (mEndTransition != null) {
mEndTransition.invalidate();
}
}
/**
@@ -271,7 +278,9 @@ public abstract class MediaItem {
*
* @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.
* 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 (mOverlays.contains(overlay)) {
@@ -283,6 +292,23 @@ public abstract class MediaItem {
"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");
}
// The dimensions of the overlay bitmap must be the same as the
// media item dimensions
if (bitmap.getWidth() != getWidth() || bitmap.getHeight() != getHeight()) {
throw new IllegalArgumentException(
"Bitmap dimensions must match media item dimensions");
}
} else {
throw new IllegalArgumentException("Overlay not supported");
}
mOverlays.add(overlay);
invalidateTransitions(overlay);
}
@@ -302,6 +328,9 @@ public abstract class MediaItem {
for (Overlay overlay : mOverlays) {
if (overlay.getId().equals(overlayId)) {
mOverlays.remove(overlay);
if (overlay instanceof OverlayFrame) {
((OverlayFrame)overlay).invalidate();
}
invalidateTransitions(overlay);
return overlay;
}

View File

@@ -264,8 +264,19 @@ public class MediaVideoItem extends MediaItem {
throw new IllegalArgumentException("Invalid end time");
}
mBeginBoundaryTimeMs = beginMs;
mEndBoundaryTimeMs = endMs;
if (beginMs != mBeginBoundaryTimeMs) {
mBeginBoundaryTimeMs = beginMs;
if (mBeginTransition != null) {
mBeginTransition.invalidate();
}
}
if (endMs == mEndBoundaryTimeMs) {
mEndBoundaryTimeMs = endMs;
if (mEndTransition != null) {
mEndTransition.invalidate();
}
}
// TODO: Validate/modify the start and the end time of effects and overlays
}

View File

@@ -16,16 +16,24 @@
package android.media.videoeditor;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap.CompressFormat;
/**
* This class is used to overlay an image on top of a media item. This class
* does not manage deletion of the overlay file so application may use
* {@link #getFilename()} for this purpose.
* This class is used to overlay an image on top of a media item.
* {@hide}
*/
public class OverlayFrame extends Overlay {
// Instance variables
private final String mFilename;
private final Bitmap mBitmap;
private String mFilename;
/**
* An object of this type cannot be instantiated by using the default
@@ -33,30 +41,90 @@ public class OverlayFrame extends Overlay {
*/
@SuppressWarnings("unused")
private OverlayFrame() {
this(null, null, 0, 0);
this(null, (String)null, 0, 0);
}
/**
* Constructor for an OverlayFrame
*
* @param overlayId The overlay id
* @param filename The file name that contains the overlay. Only PNG
* supported.
* @param bitmap The bitmap to be used as an overlay. The size of the
* bitmap must equal to the size of the media item to which it is
* added. The bitmap is typically a decoded PNG file.
* @param startTimeMs The overlay start time in milliseconds
* @param durationMs The overlay duration in milliseconds
*
* @throws IllegalArgumentException if the file type is not PNG or the
* startTimeMs and durationMs are incorrect.
*/
public OverlayFrame(String overlayId, String filename, long startTimeMs, long durationMs) {
public OverlayFrame(String overlayId, Bitmap bitmap, long startTimeMs,
long durationMs) {
super(overlayId, startTimeMs, durationMs);
mBitmap = bitmap;
mFilename = null;
}
/**
* Constructor for an OverlayFrame. This constructor can be used to
* restore the overlay after it was saved internally by the video editor.
*
* @param overlayId The overlay id
* @param filename The file name that contains the overlay.
* @param startTimeMs The overlay start time in milliseconds
* @param durationMs The overlay duration in milliseconds
*
* @throws IllegalArgumentException if the file type is not PNG or the
* startTimeMs and durationMs are incorrect.
*/
OverlayFrame(String overlayId, String filename, long startTimeMs, long durationMs) {
super(overlayId, startTimeMs, durationMs);
mFilename = filename;
mBitmap = BitmapFactory.decodeFile(mFilename);
}
/**
* @return Get the overlay bitmap
*/
public Bitmap getBitmap() {
return mBitmap;
}
/**
* Get the file name of this overlay
*/
public String getFilename() {
String getFilename() {
return mFilename;
}
/**
* Save the overlay to the project folder
*
* @param editor The video editor
*
* @return
* @throws FileNotFoundException if the bitmap cannot be saved
* @throws IOException if the bitmap file cannot be saved
*/
String save(VideoEditor editor) throws FileNotFoundException, IOException {
if (mFilename != null) {
return mFilename;
}
mFilename = editor.getPath() + "/" + getId() + ".png";
// Save the image to a local file
final FileOutputStream out = new FileOutputStream(mFilename);
mBitmap.compress(CompressFormat.PNG, 100, out);
out.flush();
out.close();
return mFilename;
}
/**
* Delete the overlay file
*/
void invalidate() {
if (mFilename != null) {
new File(mFilename).delete();
}
}
}

View File

@@ -58,17 +58,19 @@ public class TransitionAlpha extends Transition {
* Constructor
*
* @param transitionId The transition id
* @param afterMediaItem The transition is applied to the end of this
* media item
* @param beforeMediaItem The transition is applied to the beginning of
* this media item
* @param afterMediaItem The transition is applied to the end of this media
* item
* @param beforeMediaItem The transition is applied to the beginning of this
* media item
* @param durationMs duration of the transition in milliseconds
* @param behavior behavior is one of the behavior defined in Transition
* class
* @param maskFilename JPEG file name
* @param maskFilename JPEG file name. The dimension of the image
* corresponds to 720p (16:9 aspect ratio). Mask files are
* shared between video editors and can be created in the
* projects folder (the parent folder for all projects).
* @param blendingPercent The blending percent applied
* @param invert true to invert the direction of the alpha blending
*
* @throws IllegalArgumentException if behavior is not supported, or if
* direction are not supported.
*/

View File

@@ -48,8 +48,8 @@ public class VideoEditorTestImpl implements VideoEditor {
private static final String TAG_PROJECT = "project";
private static final String TAG_MEDIA_ITEMS = "media_items";
private static final String TAG_MEDIA_ITEM = "media_item";
private static final String TAG_BEGIN_TRANSITION = "begin_transition";
private static final String TAG_END_TRANSITION = "end_transition";
private static final String TAG_TRANSITIONS = "transitions";
private static final String TAG_TRANSITION = "transition";
private static final String ATTR_ID = "id";
private static final String ATTR_FILENAME = "filename";
private static final String ATTR_AUDIO_WAVEFORM_FILENAME = "wavefoem";
@@ -65,6 +65,8 @@ public class VideoEditorTestImpl implements VideoEditor {
private static final String ATTR_BLENDING = "blending";
private static final String ATTR_INVERT = "invert";
private static final String ATTR_MASK = "mask";
private static final String ATTR_BEFORE_MEDIA_ITEM_ID = "before_media_item";
private static final String ATTR_AFTER_MEDIA_ITEM_ID = "after_media_item";
// Instance variables
private long mDurationMs;
@@ -400,6 +402,7 @@ public class VideoEditorTestImpl implements VideoEditor {
}
beforeMediaItem.setBeginTransition(transition);
}
computeTimelineDuration();
}
@@ -412,22 +415,25 @@ public class VideoEditorTestImpl implements VideoEditor {
}
final Transition transition = getTransition(transitionId);
if (transition != null) {
mTransitions.remove(transition);
transition.invalidate();
computeTimelineDuration();
if (transition == null) {
throw new IllegalStateException("Transition not found: " + transitionId);
}
// Cross reference the transitions
// Remove the transition references
final MediaItem afterMediaItem = transition.getAfterMediaItem();
if (afterMediaItem != null) {
afterMediaItem.setEndTransition(null);
}
final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
if (beforeMediaItem != null) {
beforeMediaItem.setBeginTransition(null);
}
mTransitions.remove(transition);
transition.invalidate();
computeTimelineDuration();
return transition;
}
@@ -543,15 +549,14 @@ public class VideoEditorTestImpl implements VideoEditor {
serializer.startTag("", TAG_PROJECT);
serializer.attribute("", ATTR_ASPECT_RATIO, Integer.toString(mAspectRatio));
boolean firstMediaItem = true;
serializer.startTag("", TAG_MEDIA_ITEMS);
for (MediaItem mediaItem : mMediaItems) {
serializer.startTag("", TAG_MEDIA_ITEM);
serializer.attribute("", ATTR_ID, mediaItem.getId());
serializer.attribute("", ATTR_TYPE, mediaItem.getClass().getSimpleName());
serializer.attribute("", ATTR_FILENAME, mediaItem.getFilename());
serializer.attribute("", ATTR_RENDERING_MODE, Integer.toString(mediaItem
.getRenderingMode()));
serializer.attribute("", ATTR_RENDERING_MODE, Integer.toString(
mediaItem.getRenderingMode()));
if (mediaItem instanceof MediaVideoItem) {
final MediaVideoItem mvi = (MediaVideoItem)mediaItem;
serializer
@@ -559,55 +564,50 @@ public class VideoEditorTestImpl implements VideoEditor {
serializer.attribute("", ATTR_END_TIME, Long.toString(mvi.getBoundaryEndTime()));
serializer.attribute("", ATTR_VOLUME, Integer.toString(mvi.getVolume()));
if (mvi.getAudioWaveformFilename() != null) {
serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME, mvi
.getAudioWaveformFilename());
serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
mvi.getAudioWaveformFilename());
}
} else if (mediaItem instanceof MediaImageItem) {
serializer.attribute("", ATTR_DURATION, Long.toString(mediaItem.getDuration()));
}
if (firstMediaItem) {
firstMediaItem = false;
final Transition beginTransition = mediaItem.getBeginTransition();
if (beginTransition != null) {
serializer.startTag("", TAG_BEGIN_TRANSITION);
serializer.attribute("", ATTR_ID, beginTransition.getId());
serializer.attribute("", ATTR_TYPE, beginTransition.getClass()
.getSimpleName());
serializer.attribute("", ATTR_DURATION, Long.toString(beginTransition
.getDuration()));
serializer.attribute("", ATTR_BEHAVIOR, Integer.toString(beginTransition
.getBehavior()));
serializer.endTag("", TAG_BEGIN_TRANSITION);
}
}
final Transition endTransition = mediaItem.getEndTransition();
if (endTransition != null) {
serializer.startTag("", TAG_END_TRANSITION);
serializer.attribute("", ATTR_ID, endTransition.getId());
serializer.attribute("", ATTR_TYPE, endTransition.getClass().getSimpleName());
serializer.attribute("", ATTR_DURATION, Long.toString(endTransition
.getDuration()));
serializer.attribute("", ATTR_BEHAVIOR, Integer.toString(endTransition
.getBehavior()));
if (endTransition instanceof TransitionSliding) {
serializer.attribute("", ATTR_DIRECTION, Integer
.toString(((TransitionSliding)endTransition).getDirection()));
} else if (endTransition instanceof TransitionAlpha) {
TransitionAlpha ta = (TransitionAlpha)endTransition;
serializer.attribute("", ATTR_BLENDING, Integer.toString(ta
.getBlendingPercent()));
serializer.attribute("", ATTR_INVERT, Boolean.toString(ta.isInvert()));
serializer.attribute("", ATTR_MASK, ta.getMaskFilename());
}
serializer.endTag("", TAG_END_TRANSITION);
}
serializer.endTag("", TAG_MEDIA_ITEM);
}
serializer.endTag("", TAG_MEDIA_ITEMS);
serializer.startTag("", TAG_TRANSITIONS);
for (Transition transition : mTransitions) {
serializer.startTag("", TAG_TRANSITION);
serializer.attribute("", ATTR_ID, transition.getId());
serializer.attribute("", ATTR_TYPE, transition.getClass().getSimpleName());
serializer.attribute("", ATTR_DURATION, Long.toString(transition.getDuration()));
serializer.attribute("", ATTR_BEHAVIOR, Integer.toString(transition.getBehavior()));
final MediaItem afterMediaItem = transition.getAfterMediaItem();
if (afterMediaItem != null) {
serializer.attribute("", ATTR_AFTER_MEDIA_ITEM_ID, afterMediaItem.getId());
}
final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
if (beforeMediaItem != null) {
serializer.attribute("", ATTR_BEFORE_MEDIA_ITEM_ID, beforeMediaItem.getId());
}
if (transition instanceof TransitionSliding) {
serializer.attribute("", ATTR_DIRECTION,
Integer.toString(((TransitionSliding)transition).getDirection()));
} else if (transition instanceof TransitionAlpha) {
TransitionAlpha ta = (TransitionAlpha)transition;
serializer.attribute("", ATTR_BLENDING, Integer.toString(ta.getBlendingPercent()));
serializer.attribute("", ATTR_INVERT, Boolean.toString(ta.isInvert()));
if (ta.getMaskFilename() != null) {
serializer.attribute("", ATTR_MASK, ta.getMaskFilename());
}
}
serializer.endTag("", TAG_TRANSITION);
}
serializer.endTag("", TAG_TRANSITIONS);
serializer.endTag("", TAG_PROJECT);
serializer.endDocument();
@@ -628,8 +628,6 @@ public class VideoEditorTestImpl implements VideoEditor {
parser.setInput(new FileInputStream(file), "UTF-8");
int eventType = parser.getEventType();
String name;
MediaItem currentMediaItem = null;
MediaItem previousMediaItem = null;
while (eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG: {
@@ -644,6 +642,7 @@ public class VideoEditorTestImpl implements VideoEditor {
final int renderingMode = Integer.parseInt(parser.getAttributeValue("",
ATTR_RENDERING_MODE));
MediaItem currentMediaItem;
if (MediaImageItem.class.getSimpleName().equals(type)) {
final long durationMs = Long.parseLong(parser.getAttributeValue("",
ATTR_DURATION));
@@ -671,29 +670,13 @@ public class VideoEditorTestImpl implements VideoEditor {
}
if (currentMediaItem != null) {
if (previousMediaItem != null) {
currentMediaItem.setBeginTransition(previousMediaItem
.getEndTransition());
}
mMediaItems.add(currentMediaItem);
}
} else if (name.equals(TAG_BEGIN_TRANSITION)) {
final Transition transition = parseTransition(parser, currentMediaItem,
null);
currentMediaItem.setBeginTransition(transition);
} else if (name.equals(TAG_END_TRANSITION)) {
final Transition transition = parseTransition(parser, previousMediaItem,
currentMediaItem);
currentMediaItem.setEndTransition(transition);
}
break;
}
case XmlPullParser.END_TAG: {
name = parser.getName();
if (name.equals(TAG_MEDIA_ITEM)) {
previousMediaItem = currentMediaItem;
currentMediaItem = null;
} else if (name.equals(TAG_TRANSITION)) {
final Transition transition = parseTransition(parser);
if (transition != null) {
mTransitions.add(transition);
}
}
break;
}
@@ -712,47 +695,72 @@ public class VideoEditorTestImpl implements VideoEditor {
* Parse the transition
*
* @param parser The parser
* @param afterMediaItem The transition is at the end of this media item
* @param beforeMediaItem The transition is at the beginning of this media
* item
* @return The transition
*/
private Transition parseTransition(XmlPullParser parser, MediaItem beforeMediaItem,
MediaItem afterMediaItem) {
private Transition parseTransition(XmlPullParser parser) {
final String transitionId = parser.getAttributeValue("", ATTR_ID);
final String type = parser.getAttributeValue("", ATTR_TYPE);
final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
final int behavior = Integer.parseInt(parser.getAttributeValue("", ATTR_BEHAVIOR));
final String beforeMediaItemId = parser.getAttributeValue("", ATTR_BEFORE_MEDIA_ITEM_ID);
final MediaItem beforeMediaItem;
if (beforeMediaItemId != null) {
beforeMediaItem = getMediaItem(beforeMediaItemId);
} else {
beforeMediaItem = null;
}
final String afterMediaItemId = parser.getAttributeValue("", ATTR_AFTER_MEDIA_ITEM_ID);
final MediaItem afterMediaItem;
if (afterMediaItemId != null) {
afterMediaItem = getMediaItem(afterMediaItemId);
} else {
afterMediaItem = null;
}
final Transition transition;
if (TransitionStartCurtainOpening.class.getSimpleName().equals(type)) {
return new TransitionStartCurtainOpening(transitionId, beforeMediaItem, durationMs,
behavior);
transition = new TransitionStartCurtainOpening(transitionId, beforeMediaItem,
durationMs, behavior);
} else if (TransitionStartFadeFromBlack.class.getSimpleName().equals(type)) {
return new TransitionStartFadeFromBlack(transitionId, beforeMediaItem, durationMs,
behavior);
transition = new TransitionStartFadeFromBlack(transitionId, beforeMediaItem,
durationMs, behavior);
} else if (TransitionAlpha.class.getSimpleName().equals(type)) {
final int blending = Integer.parseInt(parser.getAttributeValue("", ATTR_BLENDING));
final String maskFilename = parser.getAttributeValue("", ATTR_MASK);
final boolean invert = Boolean.getBoolean(parser.getAttributeValue("", ATTR_INVERT));
return new TransitionAlpha(transitionId, afterMediaItem, beforeMediaItem, durationMs,
behavior, maskFilename, blending, invert);
} else if (TransitionAlpha.class.getSimpleName().equals(type)) {
return new TransitionCrossfade(transitionId, afterMediaItem, beforeMediaItem,
transition = new TransitionAlpha(transitionId, afterMediaItem, beforeMediaItem,
durationMs, behavior, maskFilename, blending, invert);
} else if (TransitionCrossfade.class.getSimpleName().equals(type)) {
transition = new TransitionCrossfade(transitionId, afterMediaItem, beforeMediaItem,
durationMs, behavior);
} else if (TransitionSliding.class.getSimpleName().equals(type)) {
final int direction = Integer.parseInt(parser.getAttributeValue("", ATTR_DIRECTION));
return new TransitionSliding(transitionId, afterMediaItem, beforeMediaItem, durationMs,
behavior, direction);
transition = new TransitionSliding(transitionId, afterMediaItem, beforeMediaItem,
durationMs, behavior, direction);
} else if (TransitionFadeToBlack.class.getSimpleName().equals(type)) {
return new TransitionFadeToBlack(transitionId, afterMediaItem, beforeMediaItem,
transition = new TransitionFadeToBlack(transitionId, afterMediaItem, beforeMediaItem,
durationMs, behavior);
} else if (TransitionEndCurtainClosing.class.getSimpleName().equals(type)) {
return new TransitionEndCurtainClosing(transitionId, beforeMediaItem, durationMs,
transition = new TransitionEndCurtainClosing(transitionId, afterMediaItem, durationMs,
behavior);
} else if (TransitionEndFadeToBlack.class.getSimpleName().equals(type)) {
return new TransitionEndFadeToBlack(transitionId, beforeMediaItem, durationMs, behavior);
transition = new TransitionEndFadeToBlack(transitionId, afterMediaItem, durationMs,
behavior);
} else {
transition = null;
}
return null;
if (beforeMediaItem != null) {
beforeMediaItem.setBeginTransition(transition);
}
if (afterMediaItem != null) {
afterMediaItem.setEndTransition(transition);
}
return transition;
}
public void cancelExport(String filename) {