/* * 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.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.graphics.Bitmap; import android.graphics.Rect; import android.media.videoeditor.MediaImageItem; import android.media.videoeditor.MediaItem; import android.media.MediaMetadataRetriever; import android.util.Log; import android.util.Xml; import android.view.Surface; import android.view.SurfaceHolder; import android.os.Debug; import android.os.SystemProperties; import android.os.Environment; /** * The VideoEditor implementation {@hide} */ public class VideoEditorImpl implements VideoEditor { /* * Logging */ private static final String TAG = "VideoEditorImpl"; /* * The project filename */ private static final String PROJECT_FILENAME = "videoeditor.xml"; /* * XML tags */ 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_TRANSITIONS = "transitions"; private static final String TAG_TRANSITION = "transition"; private static final String TAG_OVERLAYS = "overlays"; private static final String TAG_OVERLAY = "overlay"; private static final String TAG_OVERLAY_USER_ATTRIBUTES = "overlay_user_attributes"; private static final String TAG_EFFECTS = "effects"; private static final String TAG_EFFECT = "effect"; private static final String TAG_AUDIO_TRACKS = "audio_tracks"; private static final String TAG_AUDIO_TRACK = "audio_track"; private static final String ATTR_ID = "id"; private static final String ATTR_FILENAME = "filename"; private static final String ATTR_AUDIO_WAVEFORM_FILENAME = "waveform"; private static final String ATTR_RENDERING_MODE = "rendering_mode"; private static final String ATTR_ASPECT_RATIO = "aspect_ratio"; private static final String ATTR_REGENERATE_PCM = "regeneratePCMFlag"; private static final String ATTR_TYPE = "type"; private static final String ATTR_DURATION = "duration"; private static final String ATTR_START_TIME = "start_time"; private static final String ATTR_BEGIN_TIME = "begin_time"; private static final String ATTR_END_TIME = "end_time"; private static final String ATTR_VOLUME = "volume"; private static final String ATTR_BEHAVIOR = "behavior"; private static final String ATTR_DIRECTION = "direction"; 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"; private static final String ATTR_COLOR_EFFECT_TYPE = "color_type"; private static final String ATTR_COLOR_EFFECT_VALUE = "color_value"; private static final String ATTR_START_RECT_LEFT = "start_l"; private static final String ATTR_START_RECT_TOP = "start_t"; private static final String ATTR_START_RECT_RIGHT = "start_r"; private static final String ATTR_START_RECT_BOTTOM = "start_b"; private static final String ATTR_END_RECT_LEFT = "end_l"; private static final String ATTR_END_RECT_TOP = "end_t"; private static final String ATTR_END_RECT_RIGHT = "end_r"; private static final String ATTR_END_RECT_BOTTOM = "end_b"; private static final String ATTR_LOOP = "loop"; private static final String ATTR_MUTED = "muted"; private static final String ATTR_DUCK_ENABLED = "ducking_enabled"; private static final String ATTR_DUCK_THRESHOLD = "ducking_threshold"; private static final String ATTR_DUCKED_TRACK_VOLUME = "ducking_volume"; private static final String ATTR_GENERATED_IMAGE_CLIP = "generated_image_clip"; private static final String ATTR_IS_IMAGE_CLIP_GENERATED = "is_image_clip_generated"; private static final String ATTR_GENERATED_TRANSITION_CLIP = "generated_transition_clip"; private static final String ATTR_IS_TRANSITION_GENERATED = "is_transition_generated"; private static final String ATTR_OVERLAY_RGB_FILENAME = "overlay_rgb_filename"; private static final String ATTR_OVERLAY_FRAME_WIDTH = "overlay_frame_width"; private static final String ATTR_OVERLAY_FRAME_HEIGHT = "overlay_frame_height"; private static final String ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH = "resized_RGBframe_width"; private static final String ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT = "resized_RGBframe_height"; private static final int ENGINE_ACCESS_MAX_TIMEOUT_MS = 500; /* * Instance variables */ private final Semaphore mLock; private final String mProjectPath; private final List mMediaItems = new ArrayList(); private final List mAudioTracks = new ArrayList(); private final List mTransitions = new ArrayList(); private long mDurationMs; private int mAspectRatio; /* * Private Object for calling native Methods via MediaArtistNativeHelper */ private MediaArtistNativeHelper mMANativeHelper; private boolean mPreviewInProgress = false; private final boolean mMallocDebug; /** * Constructor * * @param projectPath - The path where the VideoEditor stores all files * related to the project */ public VideoEditorImpl(String projectPath) throws IOException { String s; s = SystemProperties.get("libc.debug.malloc"); if (s.equals("1")) { mMallocDebug = true; try { dumpHeap("HeapAtStart"); } catch (Exception ex) { Log.e(TAG, "dumpHeap returned error in constructor"); } } else { mMallocDebug = false; } mLock = new Semaphore(1, true); mMANativeHelper = new MediaArtistNativeHelper(projectPath, mLock, this); mProjectPath = projectPath; final File projectXml = new File(projectPath, PROJECT_FILENAME); if (projectXml.exists()) { try { load(); } catch (Exception ex) { ex.printStackTrace(); throw new IOException(ex.toString()); } } else { mAspectRatio = MediaProperties.ASPECT_RATIO_16_9; mDurationMs = 0; } } /* * @return The MediaArtistNativeHelper object */ MediaArtistNativeHelper getNativeContext() { return mMANativeHelper; } /* * {@inheritDoc} */ public synchronized void addAudioTrack(AudioTrack audioTrack) { if (audioTrack == null) { throw new IllegalArgumentException("Audio Track is null"); } if (mAudioTracks.size() == 1) { throw new IllegalArgumentException("No more tracks can be added"); } mMANativeHelper.setGeneratePreview(true); /* * Add the audio track to AudioTrack list */ mAudioTracks.add(audioTrack); /* * Form the audio PCM file path */ final String audioTrackPCMFilePath = String.format(mProjectPath + "/" + "AudioPcm" + audioTrack.getId() + ".pcm"); /* * Create PCM only if not generated in previous session */ if (new File(audioTrackPCMFilePath).exists()) { mMANativeHelper.setAudioflag(false); } } /* * {@inheritDoc} */ public synchronized void addMediaItem(MediaItem mediaItem) { /* * Validate Media Item */ if (mediaItem == null) { throw new IllegalArgumentException("Media item is null"); } /* * Add the Media item to MediaItem list */ if (mMediaItems.contains(mediaItem)) { throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId()); } mMANativeHelper.setGeneratePreview(true); /* * Invalidate the end transition if necessary */ final int mediaItemsCount = mMediaItems.size(); if (mediaItemsCount > 0) { removeTransitionAfter(mediaItemsCount - 1); } /* * Add the new media item */ mMediaItems.add(mediaItem); computeTimelineDuration(); /* * Generate project thumbnail only from first media Item on storyboard */ if (mMediaItems.size() == 1) { generateProjectThumbnail(); } } /* * {@inheritDoc} */ public synchronized void addTransition(Transition transition) { if (transition == null) { throw new IllegalArgumentException("Null Transition"); } final MediaItem beforeMediaItem = transition.getBeforeMediaItem(); final MediaItem afterMediaItem = transition.getAfterMediaItem(); /* * Check if the MediaItems are in sequence */ if (mMediaItems == null) { throw new IllegalArgumentException("No media items are added"); } if ((afterMediaItem != null) && (beforeMediaItem != null)) { final int afterMediaItemIndex = mMediaItems.indexOf(afterMediaItem); final int beforeMediaItemIndex = mMediaItems.indexOf(beforeMediaItem); if ((afterMediaItemIndex == -1) || (beforeMediaItemIndex == -1)) { throw new IllegalArgumentException ("Either of the mediaItem is not found in the list"); } if (afterMediaItemIndex != (beforeMediaItemIndex - 1) ) { throw new IllegalArgumentException("MediaItems are not in sequence"); } } mMANativeHelper.setGeneratePreview(true); mTransitions.add(transition); /* * Cross reference the transitions */ if (afterMediaItem != null) { /* * If a transition already exists at the specified position then * invalidate it. */ if (afterMediaItem.getEndTransition() != null) { afterMediaItem.getEndTransition().invalidate(); mTransitions.remove(afterMediaItem.getEndTransition()); } afterMediaItem.setEndTransition(transition); } if (beforeMediaItem != null) { /* * If a transition already exists at the specified position then * invalidate it. */ if (beforeMediaItem.getBeginTransition() != null) { beforeMediaItem.getBeginTransition().invalidate(); mTransitions.remove(beforeMediaItem.getBeginTransition()); } beforeMediaItem.setBeginTransition(transition); } computeTimelineDuration(); } /* * {@inheritDoc} */ public void cancelExport(String filename) { if (mMANativeHelper != null && filename != null) { mMANativeHelper.stop(filename); } } /* * {@inheritDoc} */ public void export(String filename, int height, int bitrate, int audioCodec, int videoCodec, ExportProgressListener listener) throws IOException { int audcodec = 0; int vidcodec = 0; if (filename == null) { throw new IllegalArgumentException("export: filename is null"); } final File tempPathFile = new File(filename); if (tempPathFile == null) { throw new IOException(filename + "can not be created"); } if (mMediaItems.size() == 0) { throw new IllegalStateException("No MediaItems added"); } switch (height) { case MediaProperties.HEIGHT_144: break; case MediaProperties.HEIGHT_288: break; case MediaProperties.HEIGHT_360: break; case MediaProperties.HEIGHT_480: break; case MediaProperties.HEIGHT_720: break; case MediaProperties.HEIGHT_1080: break; default: { String message = "Unsupported height value " + height; throw new IllegalArgumentException(message); } } switch (bitrate) { case MediaProperties.BITRATE_28K: break; case MediaProperties.BITRATE_40K: break; case MediaProperties.BITRATE_64K: break; case MediaProperties.BITRATE_96K: break; case MediaProperties.BITRATE_128K: break; case MediaProperties.BITRATE_192K: break; case MediaProperties.BITRATE_256K: break; case MediaProperties.BITRATE_384K: break; case MediaProperties.BITRATE_512K: break; case MediaProperties.BITRATE_800K: break; case MediaProperties.BITRATE_2M: break; case MediaProperties.BITRATE_5M: break; case MediaProperties.BITRATE_8M: break; default: { final String message = "Unsupported bitrate value " + bitrate; throw new IllegalArgumentException(message); } } computeTimelineDuration(); final long audioBitrate = MediaArtistNativeHelper.Bitrate.BR_96_KBPS; final long fileSize = (mDurationMs * (bitrate + audioBitrate)) / 8000; if (MAX_SUPPORTED_FILE_SIZE <= fileSize) { throw new IllegalStateException("Export Size is more than 2GB"); } switch (audioCodec) { case MediaProperties.ACODEC_AAC_LC: audcodec = MediaArtistNativeHelper.AudioFormat.AAC; break; case MediaProperties.ACODEC_AMRNB: audcodec = MediaArtistNativeHelper.AudioFormat.AMR_NB; break; default: { String message = "Unsupported audio codec type " + audioCodec; throw new IllegalArgumentException(message); } } switch (videoCodec) { case MediaProperties.VCODEC_H263: vidcodec = MediaArtistNativeHelper.VideoFormat.H263; break; case MediaProperties.VCODEC_H264: vidcodec = MediaArtistNativeHelper.VideoFormat.H264; break; case MediaProperties.VCODEC_MPEG4: vidcodec = MediaArtistNativeHelper.VideoFormat.MPEG4; break; default: { String message = "Unsupported video codec type " + videoCodec; throw new IllegalArgumentException(message); } } boolean semAcquireDone = false; try { lock(); semAcquireDone = true; if (mMANativeHelper == null) { throw new IllegalStateException("The video editor is not initialized"); } mMANativeHelper.setAudioCodec(audcodec); mMANativeHelper.setVideoCodec(vidcodec); mMANativeHelper.export(filename, mProjectPath, height,bitrate, mMediaItems, mTransitions, mAudioTracks, listener); } catch (InterruptedException ex) { Log.e(TAG, "Sem acquire NOT successful in export"); } finally { if (semAcquireDone) { unlock(); } } } /* * {@inheritDoc} */ public void export(String filename, int height, int bitrate, ExportProgressListener listener) throws IOException { int defaultAudiocodec = MediaArtistNativeHelper.AudioFormat.AAC; int defaultVideocodec = MediaArtistNativeHelper.VideoFormat.H264; export(filename, height, bitrate, defaultAudiocodec, defaultVideocodec, listener); } /* * {@inheritDoc} */ public void generatePreview(MediaProcessingProgressListener listener) { boolean semAcquireDone = false; try { lock(); semAcquireDone = true; if (mMANativeHelper == null) { throw new IllegalStateException("The video editor is not initialized"); } if ((mMediaItems.size() > 0) || (mAudioTracks.size() > 0)) { mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions, mAudioTracks, listener); } } catch (InterruptedException ex) { Log.e(TAG, "Sem acquire NOT successful in previewStoryBoard"); } finally { if (semAcquireDone) { unlock(); } } } /* * {@inheritDoc} */ public List getAllAudioTracks() { return mAudioTracks; } /* * {@inheritDoc} */ public List getAllMediaItems() { return mMediaItems; } /* * {@inheritDoc} */ public List getAllTransitions() { return mTransitions; } /* * {@inheritDoc} */ public int getAspectRatio() { return mAspectRatio; } /* * {@inheritDoc} */ public AudioTrack getAudioTrack(String audioTrackId) { for (AudioTrack at : mAudioTracks) { if (at.getId().equals(audioTrackId)) { return at; } } return null; } /* * {@inheritDoc} */ public long getDuration() { /** * Since MediaImageItem can change duration we need to compute the * duration here */ computeTimelineDuration(); return mDurationMs; } /* * Force updates the timeline duration */ void updateTimelineDuration() { computeTimelineDuration(); } /* * {@inheritDoc} */ public synchronized MediaItem getMediaItem(String mediaItemId) { for (MediaItem mediaItem : mMediaItems) { if (mediaItem.getId().equals(mediaItemId)) { return mediaItem; } } return null; } /* * {@inheritDoc} */ public String getPath() { return mProjectPath; } /* * {@inheritDoc} */ public Transition getTransition(String transitionId) { for (Transition transition : mTransitions) { if (transition.getId().equals(transitionId)) { return transition; } } return null; } /* * {@inheritDoc} */ public synchronized void insertAudioTrack(AudioTrack audioTrack, String afterAudioTrackId) { if (mAudioTracks.size() == 1) { throw new IllegalArgumentException("No more tracks can be added"); } if (afterAudioTrackId == null) { mMANativeHelper.setGeneratePreview(true); mAudioTracks.add(0, audioTrack); } else { final int audioTrackCount = mAudioTracks.size(); for (int i = 0; i < audioTrackCount; i++) { AudioTrack at = mAudioTracks.get(i); if (at.getId().equals(afterAudioTrackId)) { mMANativeHelper.setGeneratePreview(true); mAudioTracks.add(i + 1, audioTrack); return; } } throw new IllegalArgumentException("AudioTrack not found: " + afterAudioTrackId); } } /* * {@inheritDoc} */ public synchronized void insertMediaItem(MediaItem mediaItem, String afterMediaItemId) { if (mMediaItems.contains(mediaItem)) { throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId()); } if (afterMediaItemId == null) { mMANativeHelper.setGeneratePreview(true); if (mMediaItems.size() > 0) { /** * Invalidate the transition at the beginning of the timeline */ removeTransitionBefore(0); } mMediaItems.add(0, mediaItem); computeTimelineDuration(); generateProjectThumbnail(); } else { final int mediaItemCount = mMediaItems.size(); for (int i = 0; i < mediaItemCount; i++) { final MediaItem mi = mMediaItems.get(i); if (mi.getId().equals(afterMediaItemId)) { mMANativeHelper.setGeneratePreview(true); /** * Invalidate the transition at this position */ removeTransitionAfter(i); /** * Insert the new media item */ mMediaItems.add(i + 1, mediaItem); computeTimelineDuration(); return; } } throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId); } } /* * {@inheritDoc} */ public synchronized void moveAudioTrack(String audioTrackId, String afterAudioTrackId) { throw new IllegalStateException("Not supported"); } /* * {@inheritDoc} */ public synchronized void moveMediaItem(String mediaItemId, String afterMediaItemId) { final MediaItem moveMediaItem = removeMediaItem(mediaItemId,true); if (moveMediaItem == null) { throw new IllegalArgumentException("Target MediaItem not found: " + mediaItemId); } if (afterMediaItemId == null) { if (mMediaItems.size() > 0) { mMANativeHelper.setGeneratePreview(true); /** * Invalidate adjacent transitions at the insertion point */ removeTransitionBefore(0); /** * Insert the media item at the new position */ mMediaItems.add(0, moveMediaItem); computeTimelineDuration(); generateProjectThumbnail(); } else { throw new IllegalStateException("Cannot move media item (it is the only item)"); } } else { final int mediaItemCount = mMediaItems.size(); for (int i = 0; i < mediaItemCount; i++) { final MediaItem mi = mMediaItems.get(i); if (mi.getId().equals(afterMediaItemId)) { mMANativeHelper.setGeneratePreview(true); /** * Invalidate adjacent transitions at the insertion point */ removeTransitionAfter(i); /** * Insert the media item at the new position */ mMediaItems.add(i + 1, moveMediaItem); computeTimelineDuration(); return; } } throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId); } } /* * {@inheritDoc} */ public void release() { stopPreview(); boolean semAcquireDone = false; try { lock(); semAcquireDone = true; if (mMANativeHelper != null) { mMediaItems.clear(); mAudioTracks.clear(); mTransitions.clear(); mMANativeHelper.releaseNativeHelper(); mMANativeHelper = null; } } catch (Exception ex) { Log.e(TAG, "Sem acquire NOT successful in export", ex); } finally { if (semAcquireDone) { unlock(); } } if (mMallocDebug) { try { dumpHeap("HeapAtEnd"); } catch (Exception ex) { Log.e(TAG, "dumpHeap returned error in release"); } } } /* * {@inheritDoc} */ public synchronized void removeAllMediaItems() { mMANativeHelper.setGeneratePreview(true); mMediaItems.clear(); /** * Invalidate all transitions */ for (Transition transition : mTransitions) { transition.invalidate(); } mTransitions.clear(); mDurationMs = 0; /** * If a thumbnail already exists, then delete it */ if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) { (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete(); } } /* * {@inheritDoc} */ public synchronized AudioTrack removeAudioTrack(String audioTrackId) { final AudioTrack audioTrack = getAudioTrack(audioTrackId); if (audioTrack != null) { mMANativeHelper.setGeneratePreview(true); mAudioTracks.remove(audioTrack); audioTrack.invalidate(); mMANativeHelper.invalidatePcmFile(); mMANativeHelper.setAudioflag(true); } else { throw new IllegalArgumentException(" No more audio tracks"); } return audioTrack; } /* * {@inheritDoc} */ public synchronized MediaItem removeMediaItem(String mediaItemId) { final String firstItemString = mMediaItems.get(0).getId(); final MediaItem mediaItem = getMediaItem(mediaItemId); if (mediaItem != null) { mMANativeHelper.setGeneratePreview(true); /** * Remove the media item */ mMediaItems.remove(mediaItem); if (mediaItem instanceof MediaImageItem) { ((MediaImageItem)mediaItem).invalidate(); } final List overlays = mediaItem.getAllOverlays(); if (overlays.size() > 0) { for (Overlay overlay : overlays) { if (overlay instanceof OverlayFrame) { final OverlayFrame overlayFrame = (OverlayFrame)overlay; overlayFrame.invalidate(); } } } /** * Remove the adjacent transitions */ removeAdjacentTransitions(mediaItem); computeTimelineDuration(); } /** * If string equals first mediaItem, then * generate Project thumbnail */ if (firstItemString.equals(mediaItemId)) { generateProjectThumbnail(); } if (mediaItem instanceof MediaVideoItem) { /** * Delete the graph file */ ((MediaVideoItem)mediaItem).invalidate(); } return mediaItem; } private synchronized MediaItem removeMediaItem(String mediaItemId, boolean flag) { final String firstItemString = mMediaItems.get(0).getId(); final MediaItem mediaItem = getMediaItem(mediaItemId); if (mediaItem != null) { mMANativeHelper.setGeneratePreview(true); /** * Remove the media item */ mMediaItems.remove(mediaItem); /** * Remove the adjacent transitions */ removeAdjacentTransitions(mediaItem); computeTimelineDuration(); } /** * If string equals first mediaItem, then * generate Project thumbail */ if (firstItemString.equals(mediaItemId)) { generateProjectThumbnail(); } return mediaItem; } /* * {@inheritDoc} */ public synchronized Transition removeTransition(String transitionId) { final Transition transition = getTransition(transitionId); if (transition == null) { throw new IllegalStateException("Transition not found: " + transitionId); } mMANativeHelper.setGeneratePreview(true); /** * 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; } /* * {@inheritDoc} */ public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs, OverlayData overlayData) { if (surfaceHolder == null) { throw new IllegalArgumentException("Surface Holder is null"); } final Surface surface = surfaceHolder.getSurface(); if (surface == null) { throw new IllegalArgumentException("Surface could not be retrieved from Surface holder"); } if (surface.isValid() == false) { throw new IllegalStateException("Surface is not valid"); } if (timeMs < 0) { throw new IllegalArgumentException("requested time not correct"); } else if (timeMs > mDurationMs) { throw new IllegalArgumentException("requested time more than duration"); } long result = 0; boolean semAcquireDone = false; try { semAcquireDone = lock(ENGINE_ACCESS_MAX_TIMEOUT_MS); if (semAcquireDone == false) { throw new IllegalStateException("Timeout waiting for semaphore"); } if (mMANativeHelper == null) { throw new IllegalStateException("The video editor is not initialized"); } if (mMediaItems.size() > 0) { final Rect frame = surfaceHolder.getSurfaceFrame(); result = mMANativeHelper.renderPreviewFrame(surface, timeMs, frame.width(), frame.height(), overlayData); } else { result = 0; } } catch (InterruptedException ex) { Log.w(TAG, "The thread was interrupted", new Throwable()); throw new IllegalStateException("The thread was interrupted"); } finally { if (semAcquireDone) { unlock(); } } return result; } /** * the project form XML */ private void load() throws FileNotFoundException, XmlPullParserException, IOException { final File file = new File(mProjectPath, PROJECT_FILENAME); /** * Load the metadata */ final FileInputStream fis = new FileInputStream(file); try { final List ignoredMediaItems = new ArrayList(); final XmlPullParser parser = Xml.newPullParser(); parser.setInput(fis, "UTF-8"); int eventType = parser.getEventType(); String name; MediaItem currentMediaItem = null; Overlay currentOverlay = null; boolean regenerateProjectThumbnail = false; while (eventType != XmlPullParser.END_DOCUMENT) { switch (eventType) { case XmlPullParser.START_TAG: { name = parser.getName(); if (TAG_PROJECT.equals(name)) { mAspectRatio = Integer.parseInt(parser.getAttributeValue("", ATTR_ASPECT_RATIO)); final boolean mRegenPCM = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_REGENERATE_PCM)); mMANativeHelper.setAudioflag(mRegenPCM); } else if (TAG_MEDIA_ITEM.equals(name)) { final String mediaItemId = parser.getAttributeValue("", ATTR_ID); try { currentMediaItem = parseMediaItem(parser); mMediaItems.add(currentMediaItem); } catch (Exception ex) { Log.w(TAG, "Cannot load media item: " + mediaItemId, ex); currentMediaItem = null; // First media item is invalid, mark for project thumbnail removal if (mMediaItems.size() == 0) { regenerateProjectThumbnail = true; } // Ignore the media item ignoredMediaItems.add(mediaItemId); } } else if (TAG_TRANSITION.equals(name)) { try { final Transition transition = parseTransition(parser, ignoredMediaItems); // The transition will be null if the bounding // media items are ignored if (transition != null) { mTransitions.add(transition); } } catch (Exception ex) { Log.w(TAG, "Cannot load transition", ex); } } else if (TAG_OVERLAY.equals(name)) { if (currentMediaItem != null) { try { currentOverlay = parseOverlay(parser, currentMediaItem); currentMediaItem.addOverlay(currentOverlay); } catch (Exception ex) { Log.w(TAG, "Cannot load overlay", ex); } } } else if (TAG_OVERLAY_USER_ATTRIBUTES.equals(name)) { if (currentOverlay != null) { final int attributesCount = parser.getAttributeCount(); for (int i = 0; i < attributesCount; i++) { currentOverlay.setUserAttribute(parser.getAttributeName(i), parser.getAttributeValue(i)); } } } else if (TAG_EFFECT.equals(name)) { if (currentMediaItem != null) { try { final Effect effect = parseEffect(parser, currentMediaItem); currentMediaItem.addEffect(effect); if (effect instanceof EffectKenBurns) { final boolean isImageClipGenerated = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_IS_IMAGE_CLIP_GENERATED)); if(isImageClipGenerated) { final String filename = parser.getAttributeValue("", ATTR_GENERATED_IMAGE_CLIP); if (new File(filename).exists() == true) { ((MediaImageItem)currentMediaItem). setGeneratedImageClip(filename); ((MediaImageItem)currentMediaItem). setRegenerateClip(false); } else { ((MediaImageItem)currentMediaItem). setGeneratedImageClip(null); ((MediaImageItem)currentMediaItem). setRegenerateClip(true); } } else { ((MediaImageItem)currentMediaItem). setGeneratedImageClip(null); ((MediaImageItem)currentMediaItem). setRegenerateClip(true); } } } catch (Exception ex) { Log.w(TAG, "Cannot load effect", ex); } } } else if (TAG_AUDIO_TRACK.equals(name)) { try { final AudioTrack audioTrack = parseAudioTrack(parser); addAudioTrack(audioTrack); } catch (Exception ex) { Log.w(TAG, "Cannot load audio track", ex); } } break; } case XmlPullParser.END_TAG: { name = parser.getName(); if (TAG_MEDIA_ITEM.equals(name)) { currentMediaItem = null; } else if (TAG_OVERLAY.equals(name)) { currentOverlay = null; } break; } default: { break; } } eventType = parser.next(); } computeTimelineDuration(); // Regenerate project thumbnail if (regenerateProjectThumbnail) { generateProjectThumbnail(); regenerateProjectThumbnail = false; } } finally { if (fis != null) { fis.close(); } } } /** * Parse the media item * * @param parser The parser * @return The media item */ private MediaItem parseMediaItem(XmlPullParser parser) throws IOException { final String mediaItemId = parser.getAttributeValue("", ATTR_ID); final String type = parser.getAttributeValue("", ATTR_TYPE); final String filename = parser.getAttributeValue("", ATTR_FILENAME); final int renderingMode = Integer.parseInt(parser.getAttributeValue("", ATTR_RENDERING_MODE)); final MediaItem currentMediaItem; if (MediaImageItem.class.getSimpleName().equals(type)) { final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION)); currentMediaItem = new MediaImageItem(this, mediaItemId, filename, durationMs, renderingMode); } else if (MediaVideoItem.class.getSimpleName().equals(type)) { final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME)); final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME)); final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME)); final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED)); final String audioWaveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME); currentMediaItem = new MediaVideoItem(this, mediaItemId, filename, renderingMode, beginMs, endMs, volume, muted, audioWaveformFilename); final long beginTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME)); final long endTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME)); ((MediaVideoItem)currentMediaItem).setExtractBoundaries(beginTimeMs, endTimeMs); final int volumePercent = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME)); ((MediaVideoItem)currentMediaItem).setVolume(volumePercent); } else { throw new IllegalArgumentException("Unknown media item type: " + type); } return currentMediaItem; } /** * Parse the transition * * @param parser The parser * @param ignoredMediaItems The list of ignored media items * * @return The transition */ private Transition parseTransition(XmlPullParser parser, List ignoredMediaItems) { 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) { if (ignoredMediaItems.contains(beforeMediaItemId)) { // This transition is ignored return null; } beforeMediaItem = getMediaItem(beforeMediaItemId); } else { beforeMediaItem = null; } final String afterMediaItemId = parser.getAttributeValue("", ATTR_AFTER_MEDIA_ITEM_ID); final MediaItem afterMediaItem; if (afterMediaItemId != null) { if (ignoredMediaItems.contains(afterMediaItemId)) { // This transition is ignored return null; } afterMediaItem = getMediaItem(afterMediaItemId); } else { afterMediaItem = null; } final Transition transition; 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)); 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)); transition = new TransitionSliding(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior, direction); } else if (TransitionFadeBlack.class.getSimpleName().equals(type)) { transition = new TransitionFadeBlack(transitionId, afterMediaItem, beforeMediaItem, durationMs, behavior); } else { throw new IllegalArgumentException("Invalid transition type: " + type); } final boolean isTransitionGenerated = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_IS_TRANSITION_GENERATED)); if (isTransitionGenerated == true) { final String transitionFile = parser.getAttributeValue("", ATTR_GENERATED_TRANSITION_CLIP); if (new File(transitionFile).exists()) { transition.setFilename(transitionFile); } else { transition.setFilename(null); } } // Use the transition if (beforeMediaItem != null) { beforeMediaItem.setBeginTransition(transition); } if (afterMediaItem != null) { afterMediaItem.setEndTransition(transition); } return transition; } /** * Parse the overlay * * @param parser The parser * @param mediaItem The media item owner * * @return The overlay */ private Overlay parseOverlay(XmlPullParser parser, MediaItem mediaItem) { final String overlayId = parser.getAttributeValue("", ATTR_ID); final String type = parser.getAttributeValue("", ATTR_TYPE); final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION)); final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME)); final Overlay overlay; if (OverlayFrame.class.getSimpleName().equals(type)) { final String filename = parser.getAttributeValue("", ATTR_FILENAME); overlay = new OverlayFrame(mediaItem, overlayId, filename, startTimeMs, durationMs); } else { throw new IllegalArgumentException("Invalid overlay type: " + type); } final String overlayRgbFileName = parser.getAttributeValue("", ATTR_OVERLAY_RGB_FILENAME); if (overlayRgbFileName != null) { ((OverlayFrame)overlay).setFilename(overlayRgbFileName); final int overlayFrameWidth = Integer.parseInt(parser.getAttributeValue("", ATTR_OVERLAY_FRAME_WIDTH)); final int overlayFrameHeight = Integer.parseInt(parser.getAttributeValue("", ATTR_OVERLAY_FRAME_HEIGHT)); ((OverlayFrame)overlay).setOverlayFrameWidth(overlayFrameWidth); ((OverlayFrame)overlay).setOverlayFrameHeight(overlayFrameHeight); final int resizedRGBFrameWidth = Integer.parseInt(parser.getAttributeValue("", ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH)); final int resizedRGBFrameHeight = Integer.parseInt(parser.getAttributeValue("", ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT)); ((OverlayFrame)overlay).setResizedRGBSize(resizedRGBFrameWidth, resizedRGBFrameHeight); } return overlay; } /** * Parse the effect * * @param parser The parser * @param mediaItem The media item owner * * @return The effect */ private Effect parseEffect(XmlPullParser parser, MediaItem mediaItem) { final String effectId = parser.getAttributeValue("", ATTR_ID); final String type = parser.getAttributeValue("", ATTR_TYPE); final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION)); final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME)); final Effect effect; if (EffectColor.class.getSimpleName().equals(type)) { final int colorEffectType = Integer.parseInt(parser.getAttributeValue("", ATTR_COLOR_EFFECT_TYPE)); final int color; if (colorEffectType == EffectColor.TYPE_COLOR || colorEffectType == EffectColor.TYPE_GRADIENT) { color = Integer.parseInt(parser.getAttributeValue("", ATTR_COLOR_EFFECT_VALUE)); } else { color = 0; } effect = new EffectColor(mediaItem, effectId, startTimeMs, durationMs, colorEffectType, color); } else if (EffectKenBurns.class.getSimpleName().equals(type)) { final Rect startRect = new Rect( Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_LEFT)), Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_TOP)), Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_RIGHT)), Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_BOTTOM))); final Rect endRect = new Rect( Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_LEFT)), Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_TOP)), Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_RIGHT)), Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_BOTTOM))); effect = new EffectKenBurns(mediaItem, effectId, startRect, endRect, startTimeMs, durationMs); } else { throw new IllegalArgumentException("Invalid effect type: " + type); } return effect; } /** * Parse the audio track * * @param parser The parser * * @return The audio track */ private AudioTrack parseAudioTrack(XmlPullParser parser) throws IOException { final String audioTrackId = parser.getAttributeValue("", ATTR_ID); final String filename = parser.getAttributeValue("", ATTR_FILENAME); final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_START_TIME)); final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME)); final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME)); final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME)); final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED)); final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_LOOP)); final boolean duckingEnabled = Boolean.parseBoolean( parser.getAttributeValue("", ATTR_DUCK_ENABLED)); final int duckThreshold = Integer.parseInt( parser.getAttributeValue("", ATTR_DUCK_THRESHOLD)); final int duckedTrackVolume = Integer.parseInt(parser.getAttributeValue("", ATTR_DUCKED_TRACK_VOLUME)); final String waveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME); final AudioTrack audioTrack = new AudioTrack(this, audioTrackId, filename, startTimeMs, beginMs, endMs, loop, volume, muted, duckingEnabled, duckThreshold, duckedTrackVolume, waveformFilename); return audioTrack; } /* * {@inheritDoc} */ public void save() throws IOException { final XmlSerializer serializer = Xml.newSerializer(); final StringWriter writer = new StringWriter(); serializer.setOutput(writer); serializer.startDocument("UTF-8", true); serializer.startTag("", TAG_PROJECT); serializer.attribute("", ATTR_ASPECT_RATIO, Integer.toString(mAspectRatio)); serializer.attribute("", ATTR_REGENERATE_PCM, Boolean.toString(mMANativeHelper.getAudioflag())); 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())); if (mediaItem instanceof MediaVideoItem) { final MediaVideoItem mvi = (MediaVideoItem)mediaItem; serializer .attribute("", ATTR_BEGIN_TIME, Long.toString(mvi.getBoundaryBeginTime())); serializer.attribute("", ATTR_END_TIME, Long.toString(mvi.getBoundaryEndTime())); serializer.attribute("", ATTR_VOLUME, Integer.toString(mvi.getVolume())); serializer.attribute("", ATTR_MUTED, Boolean.toString(mvi.isMuted())); if (mvi.getAudioWaveformFilename() != null) { serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME, mvi.getAudioWaveformFilename()); } } else if (mediaItem instanceof MediaImageItem) { serializer.attribute("", ATTR_DURATION, Long.toString(mediaItem.getTimelineDuration())); } final List overlays = mediaItem.getAllOverlays(); if (overlays.size() > 0) { serializer.startTag("", TAG_OVERLAYS); for (Overlay overlay : overlays) { serializer.startTag("", TAG_OVERLAY); serializer.attribute("", ATTR_ID, overlay.getId()); serializer.attribute("", ATTR_TYPE, overlay.getClass().getSimpleName()); serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(overlay.getStartTime())); serializer.attribute("", ATTR_DURATION, Long.toString(overlay.getDuration())); if (overlay instanceof OverlayFrame) { final OverlayFrame overlayFrame = (OverlayFrame)overlay; overlayFrame.save(getPath()); if (overlayFrame.getBitmapImageFileName() != null) { serializer.attribute("", ATTR_FILENAME, overlayFrame.getBitmapImageFileName()); } if (overlayFrame.getFilename() != null) { serializer.attribute("", ATTR_OVERLAY_RGB_FILENAME, overlayFrame.getFilename()); serializer.attribute("", ATTR_OVERLAY_FRAME_WIDTH, Integer.toString(overlayFrame.getOverlayFrameWidth())); serializer.attribute("", ATTR_OVERLAY_FRAME_HEIGHT, Integer.toString(overlayFrame.getOverlayFrameHeight())); serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH, Integer.toString(overlayFrame.getResizedRGBSizeWidth())); serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT, Integer.toString(overlayFrame.getResizedRGBSizeHeight())); } } /** * Save the user attributes */ serializer.startTag("", TAG_OVERLAY_USER_ATTRIBUTES); final Map userAttributes = overlay.getUserAttributes(); for (String name : userAttributes.keySet()) { final String value = userAttributes.get(name); if (value != null) { serializer.attribute("", name, value); } } serializer.endTag("", TAG_OVERLAY_USER_ATTRIBUTES); serializer.endTag("", TAG_OVERLAY); } serializer.endTag("", TAG_OVERLAYS); } final List effects = mediaItem.getAllEffects(); if (effects.size() > 0) { serializer.startTag("", TAG_EFFECTS); for (Effect effect : effects) { serializer.startTag("", TAG_EFFECT); serializer.attribute("", ATTR_ID, effect.getId()); serializer.attribute("", ATTR_TYPE, effect.getClass().getSimpleName()); serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(effect.getStartTime())); serializer.attribute("", ATTR_DURATION, Long.toString(effect.getDuration())); if (effect instanceof EffectColor) { final EffectColor colorEffect = (EffectColor)effect; serializer.attribute("", ATTR_COLOR_EFFECT_TYPE, Integer.toString(colorEffect.getType())); if (colorEffect.getType() == EffectColor.TYPE_COLOR || colorEffect.getType() == EffectColor.TYPE_GRADIENT) { serializer.attribute("", ATTR_COLOR_EFFECT_VALUE, Integer.toString(colorEffect.getColor())); } } else if (effect instanceof EffectKenBurns) { final Rect startRect = ((EffectKenBurns)effect).getStartRect(); serializer.attribute("", ATTR_START_RECT_LEFT, Integer.toString(startRect.left)); serializer.attribute("", ATTR_START_RECT_TOP, Integer.toString(startRect.top)); serializer.attribute("", ATTR_START_RECT_RIGHT, Integer.toString(startRect.right)); serializer.attribute("", ATTR_START_RECT_BOTTOM, Integer.toString(startRect.bottom)); final Rect endRect = ((EffectKenBurns)effect).getEndRect(); serializer.attribute("", ATTR_END_RECT_LEFT, Integer.toString(endRect.left)); serializer.attribute("", ATTR_END_RECT_TOP, Integer.toString(endRect.top)); serializer.attribute("", ATTR_END_RECT_RIGHT, Integer.toString(endRect.right)); serializer.attribute("", ATTR_END_RECT_BOTTOM, Integer.toString(endRect.bottom)); final MediaItem mItem = effect.getMediaItem(); if(((MediaImageItem)mItem).getGeneratedImageClip() != null) { serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED, Boolean.toString(true)); serializer.attribute("", ATTR_GENERATED_IMAGE_CLIP, ((MediaImageItem)mItem).getGeneratedImageClip()); } else { serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED, Boolean.toString(false)); } } serializer.endTag("", TAG_EFFECT); } serializer.endTag("", TAG_EFFECTS); } 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())); serializer.attribute("", ATTR_IS_TRANSITION_GENERATED, Boolean.toString(transition.isGenerated())); if (transition.isGenerated() == true) { serializer.attribute("", ATTR_GENERATED_TRANSITION_CLIP, transition.mFilename); } 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.startTag("", TAG_AUDIO_TRACKS); for (AudioTrack at : mAudioTracks) { serializer.startTag("", TAG_AUDIO_TRACK); serializer.attribute("", ATTR_ID, at.getId()); serializer.attribute("", ATTR_FILENAME, at.getFilename()); serializer.attribute("", ATTR_START_TIME, Long.toString(at.getStartTime())); serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(at.getBoundaryBeginTime())); serializer.attribute("", ATTR_END_TIME, Long.toString(at.getBoundaryEndTime())); serializer.attribute("", ATTR_VOLUME, Integer.toString(at.getVolume())); serializer.attribute("", ATTR_DUCK_ENABLED, Boolean.toString(at.isDuckingEnabled())); serializer.attribute("", ATTR_DUCKED_TRACK_VOLUME, Integer.toString(at.getDuckedTrackVolume())); serializer.attribute("", ATTR_DUCK_THRESHOLD, Integer.toString(at.getDuckingThreshhold())); serializer.attribute("", ATTR_MUTED, Boolean.toString(at.isMuted())); serializer.attribute("", ATTR_LOOP, Boolean.toString(at.isLooping())); if (at.getAudioWaveformFilename() != null) { serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME, at.getAudioWaveformFilename()); } serializer.endTag("", TAG_AUDIO_TRACK); } serializer.endTag("", TAG_AUDIO_TRACKS); serializer.endTag("", TAG_PROJECT); serializer.endDocument(); /** * Save the metadata XML file */ final FileOutputStream out = new FileOutputStream(new File(getPath(), PROJECT_FILENAME)); out.write(writer.toString().getBytes()); out.flush(); out.close(); } /* * {@inheritDoc} */ public void setAspectRatio(int aspectRatio) { mAspectRatio = aspectRatio; /** * Invalidate all transitions */ mMANativeHelper.setGeneratePreview(true); for (Transition transition : mTransitions) { transition.invalidate(); } final Iterator it = mMediaItems.iterator(); while (it.hasNext()) { final MediaItem t = it.next(); List overlayList = t.getAllOverlays(); for (Overlay overlay : overlayList) { ((OverlayFrame)overlay).invalidateGeneratedFiles(); } } } /* * {@inheritDoc} */ public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop, int callbackAfterFrameCount, PreviewProgressListener listener) { if (surfaceHolder == null) { throw new IllegalArgumentException(); } final Surface surface = surfaceHolder.getSurface(); if (surface == null) { throw new IllegalArgumentException("Surface could not be retrieved from surface holder"); } if (surface.isValid() == false) { throw new IllegalStateException("Surface is not valid"); } if (listener == null) { throw new IllegalArgumentException(); } if (fromMs >= mDurationMs) { throw new IllegalArgumentException("Requested time not correct"); } if (fromMs < 0) { throw new IllegalArgumentException("Requested time not correct"); } boolean semAcquireDone = false; if (!mPreviewInProgress) { try{ semAcquireDone = lock(ENGINE_ACCESS_MAX_TIMEOUT_MS); if (semAcquireDone == false) { throw new IllegalStateException("Timeout waiting for semaphore"); } if (mMANativeHelper == null) { throw new IllegalStateException("The video editor is not initialized"); } if (mMediaItems.size() > 0) { mPreviewInProgress = true; mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions, mAudioTracks, null); mMANativeHelper.doPreview(surface, fromMs, toMs, loop, callbackAfterFrameCount, listener); } /** * Release The lock on complete by calling stopPreview */ } catch (InterruptedException ex) { Log.w(TAG, "The thread was interrupted", new Throwable()); throw new IllegalStateException("The thread was interrupted"); } } else { throw new IllegalStateException("Preview already in progress"); } } /* * {@inheritDoc} */ public long stopPreview() { long result = 0; if (mPreviewInProgress) { try { result = mMANativeHelper.stopPreview(); /** * release on complete by calling stopPreview */ } finally { mPreviewInProgress = false; unlock(); } return result; } else { return 0; } } /* * Remove transitions associated with the specified media item * * @param mediaItem The media item */ private void removeAdjacentTransitions(MediaItem mediaItem) { final Transition beginTransition = mediaItem.getBeginTransition(); if (beginTransition != null) { if (beginTransition.getAfterMediaItem() != null) { beginTransition.getAfterMediaItem().setEndTransition(null); } beginTransition.invalidate(); mTransitions.remove(beginTransition); } final Transition endTransition = mediaItem.getEndTransition(); if (endTransition != null) { if (endTransition.getBeforeMediaItem() != null) { endTransition.getBeforeMediaItem().setBeginTransition(null); } endTransition.invalidate(); mTransitions.remove(endTransition); } mediaItem.setBeginTransition(null); mediaItem.setEndTransition(null); } /** * Remove the transition before this media item * * @param index The media item index */ private void removeTransitionBefore(int index) { final MediaItem mediaItem = mMediaItems.get(index); final Iterator it = mTransitions.iterator(); while (it.hasNext()) { Transition t = it.next(); if (t.getBeforeMediaItem() == mediaItem) { mMANativeHelper.setGeneratePreview(true); it.remove(); t.invalidate(); mediaItem.setBeginTransition(null); if (index > 0) { mMediaItems.get(index - 1).setEndTransition(null); } break; } } } /** * Remove the transition after this media item * * @param mediaItem The media item */ private void removeTransitionAfter(int index) { final MediaItem mediaItem = mMediaItems.get(index); final Iterator it = mTransitions.iterator(); while (it.hasNext()) { Transition t = it.next(); if (t.getAfterMediaItem() == mediaItem) { mMANativeHelper.setGeneratePreview(true); it.remove(); t.invalidate(); mediaItem.setEndTransition(null); /** * Invalidate the reference in the next media item */ if (index < mMediaItems.size() - 1) { mMediaItems.get(index + 1).setBeginTransition(null); } break; } } } /** * Compute the duration */ private void computeTimelineDuration() { mDurationMs = 0; final int mediaItemsCount = mMediaItems.size(); for (int i = 0; i < mediaItemsCount; i++) { final MediaItem mediaItem = mMediaItems.get(i); mDurationMs += mediaItem.getTimelineDuration(); if (mediaItem.getEndTransition() != null) { if (i < mediaItemsCount - 1) { mDurationMs -= mediaItem.getEndTransition().getDuration(); } } } } /* * Generate the project thumbnail */ private void generateProjectThumbnail() { /* * If a thumbnail already exists, then delete it first */ if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) { (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete(); } /* * Generate a new thumbnail for the project from first media Item */ if (mMediaItems.size() > 0) { MediaItem mI = mMediaItems.get(0); /* * Keep aspect ratio of the image */ int height = 480; int width = mI.getWidth() * height / mI.getHeight(); Bitmap projectBitmap = null; String filename = mI.getFilename(); if (mI instanceof MediaVideoItem) { MediaMetadataRetriever retriever = new MediaMetadataRetriever(); retriever.setDataSource(filename); Bitmap bitmap = retriever.getFrameAtTime(); retriever.release(); retriever = null; if (bitmap == null) { String msg = "Thumbnail extraction from " + filename + " failed"; throw new IllegalArgumentException(msg); } // Resize the thumbnail to the target size projectBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true); } else { try { projectBitmap = mI.getThumbnail(width, height, 500); } catch (IllegalArgumentException e) { String msg = "Project thumbnail extraction from " + filename + " failed"; throw new IllegalArgumentException(msg); } catch (IOException e) { String msg = "IO Error creating project thumbnail"; throw new IllegalArgumentException(msg); } } try { FileOutputStream stream = new FileOutputStream(mProjectPath + "/" + THUMBNAIL_FILENAME); projectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream); stream.flush(); stream.close(); } catch (IOException e) { throw new IllegalArgumentException ("Error creating project thumbnail"); } finally { projectBitmap.recycle(); } } } /** * Clears the preview surface * * @param surfaceHolder SurfaceHolder where the preview is rendered * and needs to be cleared. */ public void clearSurface(SurfaceHolder surfaceHolder) { if (surfaceHolder == null) { throw new IllegalArgumentException("Invalid surface holder"); } final Surface surface = surfaceHolder.getSurface(); if (surface == null) { throw new IllegalArgumentException("Surface could not be retrieved from surface holder"); } if (surface.isValid() == false) { throw new IllegalStateException("Surface is not valid"); } if (mMANativeHelper != null) { mMANativeHelper.clearPreviewSurface(surface); } else { Log.w(TAG, "Native helper was not ready!"); } } /** * Grab the semaphore which arbitrates access to the editor * * @throws InterruptedException */ private void lock() throws InterruptedException { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "lock: grabbing semaphore", new Throwable()); } mLock.acquire(); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "lock: grabbed semaphore"); } } /** * Tries to grab the semaphore with a specified time out which arbitrates access to the editor * * @param timeoutMs time out in ms. * * @return true if the semaphore is acquired, false otherwise * @throws InterruptedException */ private boolean lock(long timeoutMs) throws InterruptedException { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "lock: grabbing semaphore with timeout " + timeoutMs, new Throwable()); } boolean acquireSem = mLock.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "lock: grabbed semaphore status " + acquireSem); } return acquireSem; } /** * Release the semaphore which arbitrates access to the editor */ private void unlock() { if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "unlock: releasing semaphore"); } mLock.release(); } /** * Dumps the heap memory usage information to file */ private static void dumpHeap (String filename) throws Exception { /* Cleanup as much as possible before dump */ System.gc(); System.runFinalization(); Thread.sleep(1000); String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { String extDir = Environment.getExternalStorageDirectory().toString(); /* If dump file already exists, then delete it first */ if ((new File(extDir + "/" + filename + ".dump")).exists()) { (new File(extDir + "/" + filename + ".dump")).delete(); } /* Dump native heap */ FileOutputStream ost = new FileOutputStream(extDir + "/" + filename + ".dump"); Debug.dumpNativeHeap(ost.getFD()); ost.close(); } } }