VideoEditorImpl.java revision 8afee6694b9584e7efcbf48c977780893bda4870
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package android.media.videoeditor;
19
20import java.io.File;
21import java.io.FileInputStream;
22import java.io.FileNotFoundException;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.io.StringWriter;
26import java.util.ArrayList;
27import java.util.Iterator;
28import java.util.List;
29import java.util.Map;
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32import org.xmlpull.v1.XmlSerializer;
33
34import android.graphics.Bitmap;
35import android.graphics.Rect;
36import android.util.Log;
37import android.util.Xml;
38import android.view.Surface;
39import android.view.SurfaceHolder;
40
41/**
42 * The VideoEditor implementation {@hide}
43 */
44public class VideoEditorImpl implements VideoEditor {
45    /*
46     *  Logging
47     */
48    private static final String TAG = "VideoEditorImpl";
49
50    /*
51     *  The project filename
52     */
53    private static final String PROJECT_FILENAME = "videoeditor.xml";
54
55    /*
56     *  XML tags
57     */
58    private static final String TAG_PROJECT = "project";
59    private static final String TAG_MEDIA_ITEMS = "media_items";
60    private static final String TAG_MEDIA_ITEM = "media_item";
61    private static final String TAG_TRANSITIONS = "transitions";
62    private static final String TAG_TRANSITION = "transition";
63    private static final String TAG_OVERLAYS = "overlays";
64    private static final String TAG_OVERLAY = "overlay";
65    private static final String TAG_OVERLAY_USER_ATTRIBUTES = "overlay_user_attributes";
66    private static final String TAG_EFFECTS = "effects";
67    private static final String TAG_EFFECT = "effect";
68    private static final String TAG_AUDIO_TRACKS = "audio_tracks";
69    private static final String TAG_AUDIO_TRACK = "audio_track";
70
71    private static final String ATTR_ID = "id";
72    private static final String ATTR_FILENAME = "filename";
73    private static final String ATTR_AUDIO_WAVEFORM_FILENAME = "waveform";
74    private static final String ATTR_RENDERING_MODE = "rendering_mode";
75    private static final String ATTR_ASPECT_RATIO = "aspect_ratio";
76    private static final String ATTR_REGENERATE_PCM = "regeneratePCMFlag";
77    private static final String ATTR_TYPE = "type";
78    private static final String ATTR_DURATION = "duration";
79    private static final String ATTR_START_TIME = "start_time";
80    private static final String ATTR_BEGIN_TIME = "begin_time";
81    private static final String ATTR_END_TIME = "end_time";
82    private static final String ATTR_VOLUME = "volume";
83    private static final String ATTR_BEHAVIOR = "behavior";
84    private static final String ATTR_DIRECTION = "direction";
85    private static final String ATTR_BLENDING = "blending";
86    private static final String ATTR_INVERT = "invert";
87    private static final String ATTR_MASK = "mask";
88    private static final String ATTR_BEFORE_MEDIA_ITEM_ID = "before_media_item";
89    private static final String ATTR_AFTER_MEDIA_ITEM_ID = "after_media_item";
90    private static final String ATTR_COLOR_EFFECT_TYPE = "color_type";
91    private static final String ATTR_COLOR_EFFECT_VALUE = "color_value";
92    private static final String ATTR_START_RECT_LEFT = "start_l";
93    private static final String ATTR_START_RECT_TOP = "start_t";
94    private static final String ATTR_START_RECT_RIGHT = "start_r";
95    private static final String ATTR_START_RECT_BOTTOM = "start_b";
96    private static final String ATTR_END_RECT_LEFT = "end_l";
97    private static final String ATTR_END_RECT_TOP = "end_t";
98    private static final String ATTR_END_RECT_RIGHT = "end_r";
99    private static final String ATTR_END_RECT_BOTTOM = "end_b";
100    private static final String ATTR_LOOP = "loop";
101    private static final String ATTR_MUTED = "muted";
102    private static final String ATTR_DUCK_ENABLED = "ducking_enabled";
103    private static final String ATTR_DUCK_THRESHOLD = "ducking_threshold";
104    private static final String ATTR_DUCKED_TRACK_VOLUME = "ducking_volume";
105    private static final String ATTR_GENERATED_IMAGE_CLIP = "generated_image_clip";
106    private static final String ATTR_IS_IMAGE_CLIP_GENERATED = "is_image_clip_generated";
107    private static final String ATTR_GENERATED_TRANSITION_CLIP = "generated_transition_clip";
108    private static final String ATTR_IS_TRANSITION_GENERATED = "is_transition_generated";
109    private static final String ATTR_OVERLAY_RGB_FILENAME = "overlay_rgb_filename";
110    private static final String ATTR_OVERLAY_FRAME_WIDTH = "overlay_frame_width";
111    private static final String ATTR_OVERLAY_FRAME_HEIGHT = "overlay_frame_height";
112
113    /*
114     *  Instance variables
115     */
116    private long mDurationMs;
117    private final String mProjectPath;
118    private final List<MediaItem> mMediaItems = new ArrayList<MediaItem>();
119    private final List<AudioTrack> mAudioTracks = new ArrayList<AudioTrack>();
120    private final List<Transition> mTransitions = new ArrayList<Transition>();
121    private int mAspectRatio;
122
123    /*
124     * Private Object for calling native Methods via MediaArtistNativeHelper
125     */
126    private MediaArtistNativeHelper mMANativeHelper;
127    private boolean mPreviewInProgress = false;
128
129    /**
130     * Constructor
131     *
132     * @param projectPath - The path where the VideoEditor stores all files
133     *        related to the project
134     */
135    public VideoEditorImpl(String projectPath) throws IOException {
136        mMANativeHelper = new MediaArtistNativeHelper(projectPath, this);
137        mProjectPath = projectPath;
138        final File projectXml = new File(projectPath, PROJECT_FILENAME);
139        if (projectXml.exists()) {
140            try {
141                load();
142            } catch (Exception ex) {
143                ex.printStackTrace();
144                throw new IOException(ex.toString());
145            }
146        } else {
147            mAspectRatio = MediaProperties.ASPECT_RATIO_16_9;
148            mDurationMs = 0;
149        }
150    }
151
152    /*
153     * @return The MediaArtistNativeHelper object
154     */
155    MediaArtistNativeHelper getNativeContext() {
156        return mMANativeHelper;
157    }
158
159    /*
160     * {@inheritDoc}
161     */
162    public synchronized void addAudioTrack(AudioTrack audioTrack) {
163        if (audioTrack == null) {
164            throw new IllegalArgumentException("Audio Track is null");
165        }
166
167        if (mAudioTracks.size() == 1) {
168            throw new IllegalArgumentException("No more tracks can be added");
169        }
170
171        mMANativeHelper.setGeneratePreview(true);
172
173        /*
174         * Add the audio track to AudioTrack list
175         */
176        mAudioTracks.add(audioTrack);
177
178        /*
179         * Form the audio PCM file path
180         */
181        final String audioTrackPCMFilePath = String.format(mProjectPath + "/"
182                    + "AudioPcm" + audioTrack.getId() + ".pcm");
183
184        /*
185         * Create PCM only if not generated in previous session
186         */
187        if (new File(audioTrackPCMFilePath).exists()) {
188            mMANativeHelper.setAudioflag(false);
189        }
190
191    }
192
193    /*
194     * {@inheritDoc}
195     */
196    public synchronized void addMediaItem(MediaItem mediaItem) {
197        /*
198         * Validate Media Item
199         */
200        if (mediaItem == null) {
201            throw new IllegalArgumentException("Media item is null");
202        }
203        /*
204         * Add the Media item to MediaItem list
205         */
206        if (mMediaItems.contains(mediaItem)) {
207            throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
208        }
209
210        mMANativeHelper.setGeneratePreview(true);
211
212        /*
213         *  Invalidate the end transition if necessary
214         */
215        final int mediaItemsCount = mMediaItems.size();
216        if (mediaItemsCount > 0) {
217            removeTransitionAfter(mediaItemsCount - 1);
218        }
219
220        /*
221         *  Add the new media item
222         */
223        mMediaItems.add(mediaItem);
224
225        computeTimelineDuration();
226
227        /*
228         *  Generate project thumbnail only from first media Item on storyboard
229         */
230        if (mMediaItems.size() == 1) {
231            generateProjectThumbnail();
232        }
233    }
234
235
236    /*
237     * {@inheritDoc}
238     */
239    public synchronized void addTransition(Transition transition) {
240        if (transition == null) {
241            throw new IllegalArgumentException("Null Transition");
242        }
243
244        final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
245        final MediaItem afterMediaItem = transition.getAfterMediaItem();
246        /*
247         * Check if the MediaItems are in sequence
248         */
249        if (mMediaItems == null) {
250            throw new IllegalArgumentException("No media items are added");
251        }
252
253        if ((afterMediaItem != null) &&  (beforeMediaItem != null)) {
254            final int afterMediaItemIndex = mMediaItems.indexOf(afterMediaItem);
255            final int beforeMediaItemIndex = mMediaItems.indexOf(beforeMediaItem);
256
257            if ((afterMediaItemIndex == -1) || (beforeMediaItemIndex == -1)) {
258                throw new IllegalArgumentException
259                    ("Either of the mediaItem is not found in the list");
260            }
261
262            if (afterMediaItemIndex != (beforeMediaItemIndex - 1) ) {
263                throw new IllegalArgumentException("MediaItems are not in sequence");
264            }
265        }
266
267        mMANativeHelper.setGeneratePreview(true);
268
269        mTransitions.add(transition);
270        /*
271         *  Cross reference the transitions
272         */
273        if (afterMediaItem != null) {
274            /*
275             *  If a transition already exists at the specified position then
276             *  invalidate it.
277             */
278            if (afterMediaItem.getEndTransition() != null) {
279                afterMediaItem.getEndTransition().invalidate();
280                mTransitions.remove(afterMediaItem.getEndTransition());
281            }
282            afterMediaItem.setEndTransition(transition);
283        }
284
285        if (beforeMediaItem != null) {
286            /*
287             *  If a transition already exists at the specified position then
288             *  invalidate it.
289             */
290            if (beforeMediaItem.getBeginTransition() != null) {
291                beforeMediaItem.getBeginTransition().invalidate();
292                mTransitions.remove(beforeMediaItem.getBeginTransition());
293            }
294            beforeMediaItem.setBeginTransition(transition);
295        }
296
297        computeTimelineDuration();
298    }
299
300    /*
301     * {@inheritDoc}
302     */
303    public void cancelExport(String filename) {
304        if (mMANativeHelper != null && filename != null) {
305            mMANativeHelper.stop(filename);
306        }
307    }
308
309    /*
310     * {@inheritDoc}
311     */
312    public void export(String filename, int height, int bitrate,
313                       int audioCodec, int videoCodec,
314                       ExportProgressListener listener) throws IOException {
315
316        switch (audioCodec) {
317            case MediaProperties.ACODEC_AAC_LC:
318                break;
319            case MediaProperties.ACODEC_AMRNB:
320                break;
321
322            default: {
323                String message = "Unsupported audio codec type " + audioCodec;
324                throw new IllegalArgumentException(message);
325            }
326        }
327
328        switch (videoCodec) {
329            case MediaProperties.VCODEC_H263:
330                break;
331            case MediaProperties.VCODEC_H264BP:
332                break;
333            case MediaProperties.VCODEC_MPEG4:
334                break;
335
336            default: {
337                String message = "Unsupported video codec type " + videoCodec;
338                throw new IllegalArgumentException(message);
339            }
340        }
341
342        export(filename, height, bitrate, listener);
343    }
344
345    /*
346     * {@inheritDoc}
347     */
348    public void export(String filename, int height, int bitrate,
349                       ExportProgressListener listener) throws IOException {
350        if (filename == null) {
351            throw new IllegalArgumentException("export: filename is null");
352        }
353
354        final File tempPathFile = new File(filename);
355        if (tempPathFile == null) {
356            throw new IOException(filename + "can not be created");
357        }
358
359        if (mMediaItems.size() == 0) {
360            throw new IllegalStateException("No MediaItems added");
361        }
362
363        switch (height) {
364            case MediaProperties.HEIGHT_144:
365                break;
366            case MediaProperties.HEIGHT_360:
367                break;
368            case MediaProperties.HEIGHT_480:
369                break;
370            case MediaProperties.HEIGHT_720:
371                break;
372
373            default: {
374                String message = "Unsupported height value " + height;
375                throw new IllegalArgumentException(message);
376            }
377        }
378
379        switch (bitrate) {
380            case MediaProperties.BITRATE_28K:
381                break;
382            case MediaProperties.BITRATE_40K:
383                break;
384            case MediaProperties.BITRATE_64K:
385                break;
386            case MediaProperties.BITRATE_96K:
387                break;
388            case MediaProperties.BITRATE_128K:
389                break;
390            case MediaProperties.BITRATE_192K:
391                break;
392            case MediaProperties.BITRATE_256K:
393                break;
394            case MediaProperties.BITRATE_384K:
395                break;
396            case MediaProperties.BITRATE_512K:
397                break;
398            case MediaProperties.BITRATE_800K:
399                break;
400            case MediaProperties.BITRATE_2M:
401                break;
402            case MediaProperties.BITRATE_5M:
403                break;
404            case MediaProperties.BITRATE_8M:
405                break;
406
407            default: {
408                final String message = "Unsupported bitrate value " + bitrate;
409                throw new IllegalArgumentException(message);
410            }
411        }
412
413        boolean semAcquireDone = false;
414        try {
415            mMANativeHelper.lock();
416            semAcquireDone = true;
417            mMANativeHelper.export(filename, mProjectPath, height,bitrate,
418                               mMediaItems, mTransitions, mAudioTracks, listener);
419        } catch (InterruptedException  ex) {
420            Log.e(TAG, "Sem acquire NOT successful in export");
421        } finally {
422            if (semAcquireDone) {
423                mMANativeHelper.unlock();
424            }
425        }
426    }
427
428    /*
429     * {@inheritDoc}
430     */
431    public void generatePreview(MediaProcessingProgressListener listener) {
432        boolean semAcquireDone = false;
433        try {
434            mMANativeHelper.lock();
435            semAcquireDone = true;
436
437            if ((mMediaItems.size() > 0) || (mAudioTracks.size() > 0)) {
438                mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions, mAudioTracks,
439                        listener);
440            }
441        } catch (InterruptedException  ex) {
442            Log.e(TAG, "Sem acquire NOT successful in previewStoryBoard");
443        } finally {
444            if (semAcquireDone) {
445                mMANativeHelper.unlock();
446            }
447        }
448    }
449
450    /*
451     * {@inheritDoc}
452     */
453    public List<AudioTrack> getAllAudioTracks() {
454        return mAudioTracks;
455    }
456
457    /*
458     * {@inheritDoc}
459     */
460    public List<MediaItem> getAllMediaItems() {
461        return mMediaItems;
462    }
463
464    /*
465     * {@inheritDoc}
466     */
467    public List<Transition> getAllTransitions() {
468        return mTransitions;
469    }
470
471    /*
472     * {@inheritDoc}
473     */
474    public int getAspectRatio() {
475        return mAspectRatio;
476    }
477
478    /*
479     * {@inheritDoc}
480     */
481    public AudioTrack getAudioTrack(String audioTrackId) {
482        for (AudioTrack at : mAudioTracks) {
483            if (at.getId().equals(audioTrackId)) {
484                return at;
485            }
486        }
487        return null;
488    }
489
490    /*
491     * {@inheritDoc}
492     */
493    public long getDuration() {
494        /**
495         *  Since MediaImageItem can change duration we need to compute the
496         *  duration here
497         */
498        computeTimelineDuration();
499        return mDurationMs;
500    }
501
502    /*
503     * Force updates the timeline duration
504     */
505    void updateTimelineDuration() {
506        computeTimelineDuration();
507    }
508
509    /*
510     * {@inheritDoc}
511     */
512    public synchronized MediaItem getMediaItem(String mediaItemId) {
513        for (MediaItem mediaItem : mMediaItems) {
514            if (mediaItem.getId().equals(mediaItemId)) {
515                return mediaItem;
516            }
517        }
518        return null;
519    }
520
521    /*
522     * {@inheritDoc}
523     */
524    public String getPath() {
525        return mProjectPath;
526    }
527
528    /*
529     * {@inheritDoc}
530     */
531    public Transition getTransition(String transitionId) {
532        for (Transition transition : mTransitions) {
533            if (transition.getId().equals(transitionId)) {
534                return transition;
535            }
536        }
537        return null;
538    }
539
540    /*
541     * {@inheritDoc}
542     */
543    public synchronized void insertAudioTrack(AudioTrack audioTrack,
544                                              String afterAudioTrackId) {
545        if (mAudioTracks.size() == 1) {
546            throw new IllegalArgumentException("No more tracks can be added");
547        }
548
549        if (afterAudioTrackId == null) {
550            mMANativeHelper.setGeneratePreview(true);
551            mAudioTracks.add(0, audioTrack);
552        } else {
553            final int audioTrackCount = mAudioTracks.size();
554            for (int i = 0; i < audioTrackCount; i++) {
555                AudioTrack at = mAudioTracks.get(i);
556                if (at.getId().equals(afterAudioTrackId)) {
557                    mMANativeHelper.setGeneratePreview(true);
558                    mAudioTracks.add(i + 1, audioTrack);
559                    return;
560                }
561            }
562
563            throw new IllegalArgumentException("AudioTrack not found: " + afterAudioTrackId);
564        }
565    }
566
567    /*
568     * {@inheritDoc}
569     */
570    public synchronized void insertMediaItem(MediaItem mediaItem, String afterMediaItemId) {
571        if (mMediaItems.contains(mediaItem)) {
572            throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
573        }
574
575        if (afterMediaItemId == null) {
576            mMANativeHelper.setGeneratePreview(true);
577            if (mMediaItems.size() > 0) {
578                /**
579                 *  Invalidate the transition at the beginning of the timeline
580                 */
581                removeTransitionBefore(0);
582            }
583
584            mMediaItems.add(0, mediaItem);
585            computeTimelineDuration();
586            generateProjectThumbnail();
587        } else {
588            final int mediaItemCount = mMediaItems.size();
589            for (int i = 0; i < mediaItemCount; i++) {
590                final MediaItem mi = mMediaItems.get(i);
591                if (mi.getId().equals(afterMediaItemId)) {
592                    mMANativeHelper.setGeneratePreview(true);
593                    /**
594                     *  Invalidate the transition at this position
595                     */
596                    removeTransitionAfter(i);
597                    /**
598                     *  Insert the new media item
599                     */
600                    mMediaItems.add(i + 1, mediaItem);
601                    computeTimelineDuration();
602                    return;
603                }
604            }
605
606            throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
607        }
608    }
609
610    /*
611     * {@inheritDoc}
612     */
613    public synchronized void moveAudioTrack(String audioTrackId, String afterAudioTrackId) {
614        throw new IllegalStateException("Not supported");
615    }
616
617    /*
618     * {@inheritDoc}
619     */
620    public synchronized void moveMediaItem(String mediaItemId, String afterMediaItemId) {
621        final MediaItem moveMediaItem = removeMediaItem(mediaItemId,true);
622        if (moveMediaItem == null) {
623            throw new IllegalArgumentException("Target MediaItem not found: " + mediaItemId);
624        }
625
626        if (afterMediaItemId == null) {
627            if (mMediaItems.size() > 0) {
628                mMANativeHelper.setGeneratePreview(true);
629
630                /**
631                 *  Invalidate adjacent transitions at the insertion point
632                 */
633                removeTransitionBefore(0);
634
635                /**
636                 *  Insert the media item at the new position
637                 */
638                mMediaItems.add(0, moveMediaItem);
639                computeTimelineDuration();
640
641                generateProjectThumbnail();
642            } else {
643                throw new IllegalStateException("Cannot move media item (it is the only item)");
644            }
645        } else {
646            final int mediaItemCount = mMediaItems.size();
647            for (int i = 0; i < mediaItemCount; i++) {
648                final MediaItem mi = mMediaItems.get(i);
649                if (mi.getId().equals(afterMediaItemId)) {
650                    mMANativeHelper.setGeneratePreview(true);
651                    /**
652                     *  Invalidate adjacent transitions at the insertion point
653                     */
654                    removeTransitionAfter(i);
655                    /**
656                     *  Insert the media item at the new position
657                     */
658                    mMediaItems.add(i + 1, moveMediaItem);
659                    computeTimelineDuration();
660                    return;
661                }
662            }
663
664            throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
665        }
666    }
667
668    /*
669     * {@inheritDoc}
670     */
671    public void release() {
672        stopPreview();
673        mMediaItems.clear();
674        mAudioTracks.clear();
675        mTransitions.clear();
676        mMANativeHelper.releaseNativeHelper();
677        mMANativeHelper = null;
678    }
679
680    /*
681     * {@inheritDoc}
682     */
683    public synchronized void removeAllMediaItems() {
684        mMANativeHelper.setGeneratePreview(true);
685
686        mMediaItems.clear();
687
688        /**
689         *  Invalidate all transitions
690         */
691        for (Transition transition : mTransitions) {
692            transition.invalidate();
693        }
694        mTransitions.clear();
695
696        mDurationMs = 0;
697        /**
698         * If a thumbnail already exists, then delete it
699         */
700        if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) {
701            (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete();
702        }
703
704    }
705
706    /*
707     * {@inheritDoc}
708     */
709    public synchronized AudioTrack removeAudioTrack(String audioTrackId) {
710        final AudioTrack audioTrack = getAudioTrack(audioTrackId);
711        if (audioTrack != null) {
712            mMANativeHelper.setGeneratePreview(true);
713            mAudioTracks.remove(audioTrack);
714            audioTrack.invalidate();
715            mMANativeHelper.invalidatePcmFile();
716            mMANativeHelper.setAudioflag(true);
717        } else {
718            throw new IllegalArgumentException(" No more audio tracks");
719        }
720        return audioTrack;
721    }
722
723    /*
724     * {@inheritDoc}
725     */
726    public synchronized MediaItem removeMediaItem(String mediaItemId) {
727        final String firstItemString = mMediaItems.get(0).getId();
728        final MediaItem mediaItem = getMediaItem(mediaItemId);
729        if (mediaItem != null) {
730            mMANativeHelper.setGeneratePreview(true);
731            /**
732             *  Remove the media item
733             */
734            mMediaItems.remove(mediaItem);
735            if (mediaItem instanceof MediaImageItem) {
736                ((MediaImageItem)mediaItem).invalidate();
737            }
738            final List<Overlay> overlays = mediaItem.getAllOverlays();
739            if (overlays.size() > 0) {
740                for (Overlay overlay : overlays) {
741                    if (overlay instanceof OverlayFrame) {
742                        final OverlayFrame overlayFrame = (OverlayFrame)overlay;
743                        overlayFrame.invalidate();
744                    }
745                }
746            }
747
748            /**
749             *  Remove the adjacent transitions
750             */
751            removeAdjacentTransitions(mediaItem);
752            computeTimelineDuration();
753        }
754
755        /**
756         * If string equals first mediaItem, then
757         * generate Project thumbnail
758         */
759        if (firstItemString.equals(mediaItemId)) {
760            generateProjectThumbnail();
761        }
762
763        if (mediaItem instanceof MediaVideoItem) {
764            /**
765             * Delete the graph file
766             */
767            ((MediaVideoItem)mediaItem).invalidate();
768        }
769        return mediaItem;
770    }
771
772    private synchronized MediaItem removeMediaItem(String mediaItemId,
773                                                   boolean flag) {
774        final String firstItemString = mMediaItems.get(0).getId();
775
776        final MediaItem mediaItem = getMediaItem(mediaItemId);
777        if (mediaItem != null) {
778            mMANativeHelper.setGeneratePreview(true);
779            /**
780             *  Remove the media item
781             */
782            mMediaItems.remove(mediaItem);
783            /**
784             *  Remove the adjacent transitions
785             */
786            removeAdjacentTransitions(mediaItem);
787            computeTimelineDuration();
788        }
789
790        /**
791         * If string equals first mediaItem, then
792         * generate Project thumbail
793         */
794        if (firstItemString.equals(mediaItemId)) {
795            generateProjectThumbnail();
796        }
797        return mediaItem;
798    }
799
800    /*
801     * {@inheritDoc}
802     */
803    public synchronized Transition removeTransition(String transitionId) {
804        final Transition transition = getTransition(transitionId);
805        if (transition == null) {
806            throw new IllegalStateException("Transition not found: " + transitionId);
807        }
808
809        mMANativeHelper.setGeneratePreview(true);
810
811        /**
812         *  Remove the transition references
813         */
814        final MediaItem afterMediaItem = transition.getAfterMediaItem();
815        if (afterMediaItem != null) {
816            afterMediaItem.setEndTransition(null);
817        }
818
819        final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
820        if (beforeMediaItem != null) {
821            beforeMediaItem.setBeginTransition(null);
822        }
823
824        mTransitions.remove(transition);
825        transition.invalidate();
826        computeTimelineDuration();
827        return transition;
828    }
829
830    /*
831     * {@inheritDoc}
832     */
833    public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs,
834                                    OverlayData overlayData) {
835        if (surfaceHolder == null) {
836            throw new IllegalArgumentException("Surface Holder is null");
837        }
838
839        final Surface surface = surfaceHolder.getSurface();
840        if (surface == null) {
841            throw new IllegalArgumentException("Surface could not be retrieved from Surface holder");
842        }
843
844        if (timeMs < 0) {
845            throw new IllegalArgumentException("requested time not correct");
846        } else if (timeMs > mDurationMs) {
847            throw new IllegalArgumentException("requested time more than duration");
848        }
849        long result = 0;
850
851        boolean semAcquireDone = false;
852        try {
853            mMANativeHelper.lock();
854            semAcquireDone = true;
855
856            if (mMediaItems.size() > 0) {
857                final Rect frame = surfaceHolder.getSurfaceFrame();
858                result = mMANativeHelper.renderPreviewFrame(surface,
859                        timeMs, frame.width(), frame.height(), overlayData);
860            } else {
861                result = 0;
862            }
863        } catch (InterruptedException  ex) {
864            Log.e(TAG, "Sem acquire NOT successful in renderPreviewFrame");
865        } finally {
866            if (semAcquireDone) {
867                mMANativeHelper.unlock();
868            }
869        }
870Log.i("VE_IMPL","renderPreviewFrame <--");
871        return result;
872    }
873
874    /**
875     *  the project form XML
876     */
877    private void load() throws FileNotFoundException, XmlPullParserException,
878                               IOException {
879        final File file = new File(mProjectPath, PROJECT_FILENAME);
880        /**
881         *  Load the metadata
882         */
883        final FileInputStream fis = new FileInputStream(file);
884        try {
885            final XmlPullParser parser = Xml.newPullParser();
886            parser.setInput(fis, "UTF-8");
887            int eventType = parser.getEventType();
888            String name;
889            MediaItem currentMediaItem = null;
890            Overlay currentOverlay = null;
891            while (eventType != XmlPullParser.END_DOCUMENT) {
892                switch (eventType) {
893                    case XmlPullParser.START_TAG: {
894                        name = parser.getName();
895                        if (TAG_PROJECT.equals(name)) {
896                            mAspectRatio =
897                                   Integer.parseInt(parser.getAttributeValue("",
898                                   ATTR_ASPECT_RATIO));
899
900                            final boolean mRegenPCM =
901                               Boolean.parseBoolean(parser.getAttributeValue("",
902                                    ATTR_REGENERATE_PCM));
903                            mMANativeHelper.setAudioflag(mRegenPCM);
904
905                        } else if (TAG_MEDIA_ITEM.equals(name)) {
906                            final String mediaItemId =
907                                          parser.getAttributeValue("", ATTR_ID);
908                            final String type =
909                                        parser.getAttributeValue("", ATTR_TYPE);
910                            final String filename =
911                                    parser.getAttributeValue("", ATTR_FILENAME);
912                            final int renderingMode =
913                                   Integer.parseInt(parser.getAttributeValue("",
914                                   ATTR_RENDERING_MODE));
915
916                            if (MediaImageItem.class.getSimpleName().equals(type)) {
917                                final long durationMs = Long.parseLong(parser.getAttributeValue("",
918                                        ATTR_DURATION));
919                                currentMediaItem = new MediaImageItem(this, mediaItemId, filename,
920                                        durationMs, renderingMode);
921                            } else if (MediaVideoItem.class.getSimpleName().equals(type)) {
922                                final long beginMs = Long.parseLong(parser.getAttributeValue("",
923                                        ATTR_BEGIN_TIME));
924                                final long endMs = Long.parseLong(parser.getAttributeValue("",
925                                        ATTR_END_TIME));
926                                final int volume = Integer.parseInt(parser.getAttributeValue("",
927                                        ATTR_VOLUME));
928                                final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("",
929                                        ATTR_MUTED));
930                                final String audioWaveformFilename = parser.getAttributeValue("",
931                                        ATTR_AUDIO_WAVEFORM_FILENAME);
932                                currentMediaItem = new MediaVideoItem(this, mediaItemId, filename,
933                                        renderingMode, beginMs, endMs, volume, muted,
934                                        audioWaveformFilename);
935
936                                final long beginTimeMs = Long.parseLong(parser.getAttributeValue("",
937                                        ATTR_BEGIN_TIME));
938                                final long endTimeMs = Long.parseLong(parser.getAttributeValue("",
939                                        ATTR_END_TIME));
940                                ((MediaVideoItem)currentMediaItem).setExtractBoundaries(beginTimeMs,
941                                        endTimeMs);
942
943                                final int volumePercent = Integer.parseInt(parser.getAttributeValue("",
944                                        ATTR_VOLUME));
945                                ((MediaVideoItem)currentMediaItem).setVolume(volumePercent);
946                            } else {
947                                Log.e(TAG, "Unknown media item type: " + type);
948                                currentMediaItem = null;
949                            }
950
951                            if (currentMediaItem != null) {
952                                mMediaItems.add(currentMediaItem);
953                            }
954                        } else if (TAG_TRANSITION.equals(name)) {
955                            final Transition transition = parseTransition(parser);
956                            if (transition != null) {
957                                mTransitions.add(transition);
958                            }
959                        } else if (TAG_OVERLAY.equals(name)) {
960                            if (currentMediaItem != null) {
961                                currentOverlay = parseOverlay(parser, currentMediaItem);
962                                if (currentOverlay != null) {
963                                    currentMediaItem.addOverlay(currentOverlay);
964                                }
965                            }
966                        } else if (TAG_OVERLAY_USER_ATTRIBUTES.equals(name)) {
967                            if (currentOverlay != null) {
968                                final int attributesCount = parser.getAttributeCount();
969                                for (int i = 0; i < attributesCount; i++) {
970                                    currentOverlay.setUserAttribute(parser.getAttributeName(i),
971                                            parser.getAttributeValue(i));
972                                }
973                            }
974                        } else if (TAG_EFFECT.equals(name)) {
975                            if (currentMediaItem != null) {
976                                final Effect effect = parseEffect(parser, currentMediaItem);
977                                if (effect != null) {
978                                    currentMediaItem.addEffect(effect);
979                                }
980                                if (effect instanceof EffectKenBurns) {
981                                    final boolean isImageClipGenerated =
982                                           Boolean.parseBoolean(parser.getAttributeValue("",
983                                                              ATTR_IS_IMAGE_CLIP_GENERATED));
984                                    if(isImageClipGenerated) {
985                                        String filename = parser.getAttributeValue("",
986                                                              ATTR_GENERATED_IMAGE_CLIP);
987                                        if (new File(filename).exists() == true) {
988                                            ((MediaImageItem)currentMediaItem).
989                                                        setGeneratedImageClip(filename);
990                                            ((MediaImageItem)currentMediaItem).
991                                                         setRegenerateClip(false);
992                                         } else {
993                                           ((MediaImageItem)currentMediaItem).
994                                                         setGeneratedImageClip(null);
995                                           ((MediaImageItem)currentMediaItem).
996                                                         setRegenerateClip(true);
997                                         }
998                                    } else {
999                                        ((MediaImageItem)currentMediaItem).
1000                                                         setGeneratedImageClip(null);
1001                                        ((MediaImageItem)currentMediaItem).
1002                                                        setRegenerateClip(true);
1003                                    }
1004                                }
1005                            }
1006                        } else if (TAG_AUDIO_TRACK.equals(name)) {
1007                            final AudioTrack audioTrack = parseAudioTrack(parser);
1008                            if (audioTrack != null) {
1009                                addAudioTrack(audioTrack);
1010                            }
1011                        }
1012                        break;
1013                    }
1014
1015                    case XmlPullParser.END_TAG: {
1016                        name = parser.getName();
1017                        if (TAG_MEDIA_ITEM.equals(name)) {
1018                            currentMediaItem = null;
1019                        } else if (TAG_OVERLAY.equals(name)) {
1020                            currentOverlay = null;
1021                        }
1022                        break;
1023                    }
1024
1025                    default: {
1026                        break;
1027                    }
1028                }
1029                eventType = parser.next();
1030            }
1031            computeTimelineDuration();
1032        } finally {
1033            if (fis != null) {
1034                fis.close();
1035            }
1036        }
1037    }
1038
1039    /**
1040     * Parse the transition
1041     *
1042     * @param parser The parser
1043     * @return The transition
1044     */
1045    private Transition parseTransition(XmlPullParser parser) {
1046        final String transitionId = parser.getAttributeValue("", ATTR_ID);
1047        final String type = parser.getAttributeValue("", ATTR_TYPE);
1048        final long durationMs = Long.parseLong(parser.getAttributeValue("",
1049                                               ATTR_DURATION));
1050        final int behavior = Integer.parseInt(parser.getAttributeValue("",
1051                                              ATTR_BEHAVIOR));
1052        final boolean isTransitionGenerated;
1053
1054
1055        final String beforeMediaItemId = parser.getAttributeValue("",
1056                                                     ATTR_BEFORE_MEDIA_ITEM_ID);
1057        final MediaItem beforeMediaItem;
1058        if (beforeMediaItemId != null) {
1059            beforeMediaItem = getMediaItem(beforeMediaItemId);
1060        } else {
1061            beforeMediaItem = null;
1062        }
1063
1064        final String afterMediaItemId = parser.getAttributeValue("",
1065                                                      ATTR_AFTER_MEDIA_ITEM_ID);
1066        final MediaItem afterMediaItem;
1067        if (afterMediaItemId != null) {
1068            afterMediaItem = getMediaItem(afterMediaItemId);
1069        } else {
1070            afterMediaItem = null;
1071        }
1072
1073        final Transition transition;
1074        if (TransitionAlpha.class.getSimpleName().equals(type)) {
1075            final int blending = Integer.parseInt(parser.getAttributeValue("", ATTR_BLENDING));
1076            final String maskFilename = parser.getAttributeValue("", ATTR_MASK);
1077            final boolean invert = Boolean.getBoolean(parser.getAttributeValue("", ATTR_INVERT));
1078            transition = new TransitionAlpha(transitionId, afterMediaItem, beforeMediaItem,
1079                    durationMs, behavior, maskFilename, blending, invert);
1080        } else if (TransitionCrossfade.class.getSimpleName().equals(type)) {
1081            transition = new TransitionCrossfade(transitionId, afterMediaItem, beforeMediaItem,
1082                    durationMs, behavior);
1083        } else if (TransitionSliding.class.getSimpleName().equals(type)) {
1084            final int direction = Integer.parseInt(parser.getAttributeValue("", ATTR_DIRECTION));
1085            transition = new TransitionSliding(transitionId, afterMediaItem, beforeMediaItem,
1086                    durationMs, behavior, direction);
1087        } else if (TransitionFadeBlack.class.getSimpleName().equals(type)) {
1088            transition = new TransitionFadeBlack(transitionId, afterMediaItem, beforeMediaItem,
1089                    durationMs, behavior);
1090        } else {
1091            transition = null;
1092        }
1093
1094        if (beforeMediaItem != null) {
1095            beforeMediaItem.setBeginTransition(transition);
1096        }
1097
1098        if (afterMediaItem != null) {
1099            afterMediaItem.setEndTransition(transition);
1100        }
1101
1102        isTransitionGenerated = Boolean.parseBoolean(parser.getAttributeValue("",
1103                                                 ATTR_IS_TRANSITION_GENERATED));
1104        if (isTransitionGenerated == true) {
1105            final String transitionFile = parser.getAttributeValue("",
1106                                                ATTR_GENERATED_TRANSITION_CLIP);
1107
1108            if (new File(transitionFile).exists()) {
1109                transition.setFilename(transitionFile);
1110            } else {
1111                transition.setFilename(null);
1112            }
1113        }
1114        return transition;
1115    }
1116
1117
1118    /**
1119     * Parse the overlay
1120     *
1121     * @param parser The parser
1122     * @param mediaItem The media item owner
1123     *
1124     * @return The overlay
1125     */
1126    private Overlay parseOverlay(XmlPullParser parser, MediaItem mediaItem) {
1127        final String overlayId = parser.getAttributeValue("", ATTR_ID);
1128        final String type = parser.getAttributeValue("", ATTR_TYPE);
1129        final long durationMs = Long.parseLong(parser.getAttributeValue("",
1130                                                                ATTR_DURATION));
1131        final long startTimeMs = Long.parseLong(parser.getAttributeValue("",
1132                                                              ATTR_BEGIN_TIME));
1133
1134        final Overlay overlay;
1135        if (OverlayFrame.class.getSimpleName().equals(type)) {
1136            final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1137            overlay = new OverlayFrame(mediaItem, overlayId, filename,
1138                                       startTimeMs, durationMs);
1139        } else {
1140            overlay = null;
1141        }
1142
1143        final String overlayRgbFileName = parser.getAttributeValue("",
1144                                                     ATTR_OVERLAY_RGB_FILENAME);
1145        if (overlayRgbFileName != null) {
1146            ((OverlayFrame)overlay).setFilename(overlayRgbFileName);
1147
1148            final int overlayFrameWidth =
1149                                   Integer.parseInt(parser.getAttributeValue("",
1150                                   ATTR_OVERLAY_FRAME_WIDTH));
1151            final int overlayFrameHeight =
1152                                   Integer.parseInt(parser.getAttributeValue("",
1153                                   ATTR_OVERLAY_FRAME_HEIGHT));
1154
1155            ((OverlayFrame)overlay).setOverlayFrameWidth(overlayFrameWidth);
1156            ((OverlayFrame)overlay).setOverlayFrameHeight(overlayFrameHeight);
1157        }
1158
1159        return overlay;
1160    }
1161
1162    /**
1163     * Parse the effect
1164     *
1165     * @param parser The parser
1166     * @param mediaItem The media item owner
1167     *
1168     * @return The effect
1169     */
1170    private Effect parseEffect(XmlPullParser parser, MediaItem mediaItem) {
1171        final String effectId = parser.getAttributeValue("", ATTR_ID);
1172        final String type = parser.getAttributeValue("", ATTR_TYPE);
1173        final long durationMs = Long.parseLong(parser.getAttributeValue("",
1174                                                                ATTR_DURATION));
1175        final long startTimeMs = Long.parseLong(parser.getAttributeValue("",
1176                                                              ATTR_BEGIN_TIME));
1177
1178        final Effect effect;
1179        if (EffectColor.class.getSimpleName().equals(type)) {
1180            final int colorEffectType =
1181                Integer.parseInt(parser.getAttributeValue("",
1182                                                       ATTR_COLOR_EFFECT_TYPE));
1183            final int color;
1184            if (colorEffectType == EffectColor.TYPE_COLOR
1185                    || colorEffectType == EffectColor.TYPE_GRADIENT) {
1186                color = Integer.parseInt(parser.getAttributeValue("",
1187                                                      ATTR_COLOR_EFFECT_VALUE));
1188            } else {
1189                color = 0;
1190            }
1191            effect = new EffectColor(mediaItem, effectId, startTimeMs,
1192                    durationMs, colorEffectType, color);
1193        } else if (EffectKenBurns.class.getSimpleName().equals(type)) {
1194            final Rect startRect = new Rect(
1195                    Integer.parseInt(parser.getAttributeValue("",
1196                                                         ATTR_START_RECT_LEFT)),
1197                    Integer.parseInt(parser.getAttributeValue("",
1198                                                          ATTR_START_RECT_TOP)),
1199                    Integer.parseInt(parser.getAttributeValue("",
1200                                                        ATTR_START_RECT_RIGHT)),
1201                    Integer.parseInt(parser.getAttributeValue("",
1202                                                      ATTR_START_RECT_BOTTOM)));
1203            final Rect endRect = new Rect(
1204                    Integer.parseInt(parser.getAttributeValue("",
1205                                                           ATTR_END_RECT_LEFT)),
1206                    Integer.parseInt(parser.getAttributeValue("",
1207                                                            ATTR_END_RECT_TOP)),
1208                    Integer.parseInt(parser.getAttributeValue("",
1209                                                          ATTR_END_RECT_RIGHT)),
1210                    Integer.parseInt(parser.getAttributeValue("",
1211                                                        ATTR_END_RECT_BOTTOM)));
1212            effect = new EffectKenBurns(mediaItem, effectId, startRect, endRect,
1213                                        startTimeMs, durationMs);
1214        } else {
1215            effect = null;
1216        }
1217
1218        return effect;
1219    }
1220
1221    /**
1222     * Parse the audio track
1223     *
1224     * @param parser The parser
1225     *
1226     * @return The audio track
1227     */
1228    private AudioTrack parseAudioTrack(XmlPullParser parser) {
1229        final String audioTrackId = parser.getAttributeValue("", ATTR_ID);
1230        final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1231        final long startTimeMs = Long.parseLong(parser.getAttributeValue("",
1232                                                              ATTR_START_TIME));
1233        final long beginMs = Long.parseLong(parser.getAttributeValue("",
1234                                                              ATTR_BEGIN_TIME));
1235        final long endMs = Long.parseLong(parser.getAttributeValue("",
1236                                                                ATTR_END_TIME));
1237        final int volume = Integer.parseInt(parser.getAttributeValue("",
1238                                                                  ATTR_VOLUME));
1239        final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("",
1240                                                                   ATTR_MUTED));
1241        final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("",
1242                                                                    ATTR_LOOP));
1243        final boolean duckingEnabled =
1244                               Boolean.parseBoolean(parser.getAttributeValue("",
1245                                                    ATTR_DUCK_ENABLED));
1246        final int duckThreshold = Integer.parseInt(parser.getAttributeValue("",
1247                                                          ATTR_DUCK_THRESHOLD));
1248        final int duckedTrackVolume =
1249                                   Integer.parseInt(parser.getAttributeValue("",
1250                                                     ATTR_DUCKED_TRACK_VOLUME));
1251
1252        final String waveformFilename = parser.getAttributeValue("",
1253                                                  ATTR_AUDIO_WAVEFORM_FILENAME);
1254        try {
1255            final AudioTrack audioTrack = new AudioTrack(this, audioTrackId,
1256                                                         filename, startTimeMs,
1257                                                         beginMs, endMs, loop,
1258                                                         volume, muted,
1259                                                         duckingEnabled,
1260                                                         duckThreshold,
1261                                                         duckedTrackVolume,
1262                                                         waveformFilename);
1263
1264            return audioTrack;
1265        } catch (IOException ex) {
1266            return null;
1267        }
1268    }
1269
1270
1271    /*
1272     * {@inheritDoc}
1273     */
1274    public void save() throws IOException {
1275        final XmlSerializer serializer = Xml.newSerializer();
1276        final StringWriter writer = new StringWriter();
1277        serializer.setOutput(writer);
1278        serializer.startDocument("UTF-8", true);
1279        serializer.startTag("", TAG_PROJECT);
1280        serializer.attribute("",
1281                             ATTR_ASPECT_RATIO, Integer.toString(mAspectRatio));
1282
1283        serializer.attribute("", ATTR_REGENERATE_PCM,
1284                        Boolean.toString(mMANativeHelper.getAudioflag()));
1285
1286        serializer.startTag("", TAG_MEDIA_ITEMS);
1287        for (MediaItem mediaItem : mMediaItems) {
1288            serializer.startTag("", TAG_MEDIA_ITEM);
1289            serializer.attribute("", ATTR_ID, mediaItem.getId());
1290            serializer.attribute("", ATTR_TYPE,
1291                                          mediaItem.getClass().getSimpleName());
1292            serializer.attribute("", ATTR_FILENAME, mediaItem.getFilename());
1293            serializer.attribute("", ATTR_RENDERING_MODE, Integer.toString(
1294                    mediaItem.getRenderingMode()));
1295            if (mediaItem instanceof MediaVideoItem) {
1296                final MediaVideoItem mvi = (MediaVideoItem)mediaItem;
1297                serializer
1298                .attribute("", ATTR_BEGIN_TIME,
1299                                     Long.toString(mvi.getBoundaryBeginTime()));
1300                serializer.attribute("", ATTR_END_TIME,
1301                                       Long.toString(mvi.getBoundaryEndTime()));
1302                serializer.attribute("", ATTR_VOLUME,
1303                                             Integer.toString(mvi.getVolume()));
1304                serializer.attribute("", ATTR_MUTED,
1305                                               Boolean.toString(mvi.isMuted()));
1306                if (mvi.getAudioWaveformFilename() != null) {
1307                    serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
1308                            mvi.getAudioWaveformFilename());
1309                }
1310            } else if (mediaItem instanceof MediaImageItem) {
1311                serializer.attribute("", ATTR_DURATION,
1312                        Long.toString(mediaItem.getTimelineDuration()));
1313            }
1314
1315            final List<Overlay> overlays = mediaItem.getAllOverlays();
1316            if (overlays.size() > 0) {
1317                serializer.startTag("", TAG_OVERLAYS);
1318                for (Overlay overlay : overlays) {
1319                    serializer.startTag("", TAG_OVERLAY);
1320                    serializer.attribute("", ATTR_ID, overlay.getId());
1321                    serializer.attribute("",
1322                                 ATTR_TYPE, overlay.getClass().getSimpleName());
1323                    serializer.attribute("", ATTR_BEGIN_TIME,
1324                                         Long.toString(overlay.getStartTime()));
1325                    serializer.attribute("", ATTR_DURATION,
1326                                          Long.toString(overlay.getDuration()));
1327                    if (overlay instanceof OverlayFrame) {
1328                        final OverlayFrame overlayFrame = (OverlayFrame)overlay;
1329                        overlayFrame.save(getPath());
1330                        if (overlayFrame.getBitmapImageFileName() != null) {
1331                            serializer.attribute("", ATTR_FILENAME,
1332                                         overlayFrame.getBitmapImageFileName());
1333                        }
1334
1335                        if (overlayFrame.getFilename() != null) {
1336                            serializer.attribute("",
1337                                                 ATTR_OVERLAY_RGB_FILENAME,
1338                                                 overlayFrame.getFilename());
1339                            serializer.attribute("", ATTR_OVERLAY_FRAME_WIDTH,
1340                                                 Integer.toString(overlayFrame.getOverlayFrameWidth()));
1341                            serializer.attribute("", ATTR_OVERLAY_FRAME_HEIGHT,
1342                                                 Integer.toString(overlayFrame.getOverlayFrameHeight()));
1343                        }
1344
1345                    }
1346
1347                    /**
1348                     *  Save the user attributes
1349                     */
1350                    serializer.startTag("", TAG_OVERLAY_USER_ATTRIBUTES);
1351                    final Map<String, String> userAttributes = overlay.getUserAttributes();
1352                    for (String name : userAttributes.keySet()) {
1353                        final String value = userAttributes.get(name);
1354                        if (value != null) {
1355                            serializer.attribute("", name, value);
1356                        }
1357                    }
1358                    serializer.endTag("", TAG_OVERLAY_USER_ATTRIBUTES);
1359
1360                    serializer.endTag("", TAG_OVERLAY);
1361                }
1362                serializer.endTag("", TAG_OVERLAYS);
1363            }
1364
1365            final List<Effect> effects = mediaItem.getAllEffects();
1366            if (effects.size() > 0) {
1367                serializer.startTag("", TAG_EFFECTS);
1368                for (Effect effect : effects) {
1369                    serializer.startTag("", TAG_EFFECT);
1370                    serializer.attribute("", ATTR_ID, effect.getId());
1371                    serializer.attribute("",
1372                                  ATTR_TYPE, effect.getClass().getSimpleName());
1373                    serializer.attribute("", ATTR_BEGIN_TIME,
1374                            Long.toString(effect.getStartTime()));
1375                    serializer.attribute("", ATTR_DURATION,
1376                                           Long.toString(effect.getDuration()));
1377                    if (effect instanceof EffectColor) {
1378                        final EffectColor colorEffect = (EffectColor)effect;
1379                        serializer.attribute("", ATTR_COLOR_EFFECT_TYPE,
1380                                Integer.toString(colorEffect.getType()));
1381                        if (colorEffect.getType() == EffectColor.TYPE_COLOR ||
1382                                colorEffect.getType() == EffectColor.TYPE_GRADIENT) {
1383                            serializer.attribute("", ATTR_COLOR_EFFECT_VALUE,
1384                                    Integer.toString(colorEffect.getColor()));
1385                        }
1386                    } else if (effect instanceof EffectKenBurns) {
1387                        final Rect startRect = ((EffectKenBurns)effect).getStartRect();
1388                        serializer.attribute("", ATTR_START_RECT_LEFT,
1389                                Integer.toString(startRect.left));
1390                        serializer.attribute("", ATTR_START_RECT_TOP,
1391                                Integer.toString(startRect.top));
1392                        serializer.attribute("", ATTR_START_RECT_RIGHT,
1393                                Integer.toString(startRect.right));
1394                        serializer.attribute("", ATTR_START_RECT_BOTTOM,
1395                                Integer.toString(startRect.bottom));
1396
1397                        final Rect endRect = ((EffectKenBurns)effect).getEndRect();
1398                        serializer.attribute("", ATTR_END_RECT_LEFT,
1399                                                Integer.toString(endRect.left));
1400                        serializer.attribute("", ATTR_END_RECT_TOP,
1401                                                 Integer.toString(endRect.top));
1402                        serializer.attribute("", ATTR_END_RECT_RIGHT,
1403                                               Integer.toString(endRect.right));
1404                        serializer.attribute("", ATTR_END_RECT_BOTTOM,
1405                                Integer.toString(endRect.bottom));
1406                        final MediaItem mItem = effect.getMediaItem();
1407                           if(((MediaImageItem)mItem).getGeneratedImageClip() != null) {
1408                               serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED,Boolean.toString(true));
1409                               serializer.attribute("", ATTR_GENERATED_IMAGE_CLIP,
1410                                     ((MediaImageItem)mItem).getGeneratedImageClip());
1411                            } else {
1412                                serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED,
1413                                     Boolean.toString(false));
1414                         }
1415                    }
1416
1417                    serializer.endTag("", TAG_EFFECT);
1418                }
1419                serializer.endTag("", TAG_EFFECTS);
1420            }
1421
1422            serializer.endTag("", TAG_MEDIA_ITEM);
1423        }
1424        serializer.endTag("", TAG_MEDIA_ITEMS);
1425
1426        serializer.startTag("", TAG_TRANSITIONS);
1427
1428        for (Transition transition : mTransitions) {
1429            serializer.startTag("", TAG_TRANSITION);
1430            serializer.attribute("", ATTR_ID, transition.getId());
1431            serializer.attribute("", ATTR_TYPE,
1432                                         transition.getClass().getSimpleName());
1433            serializer.attribute("", ATTR_DURATION,
1434                                       Long.toString(transition.getDuration()));
1435            serializer.attribute("", ATTR_BEHAVIOR,
1436                                    Integer.toString(transition.getBehavior()));
1437            serializer.attribute("", ATTR_IS_TRANSITION_GENERATED,
1438                                    Boolean.toString(transition.isGenerated()));
1439            if (transition.isGenerated() == true) {
1440                serializer.attribute("", ATTR_GENERATED_TRANSITION_CLIP,
1441                                                          transition.mFilename);
1442            }
1443            final MediaItem afterMediaItem = transition.getAfterMediaItem();
1444            if (afterMediaItem != null) {
1445                serializer.attribute("", ATTR_AFTER_MEDIA_ITEM_ID,
1446                                                        afterMediaItem.getId());
1447            }
1448
1449            final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
1450            if (beforeMediaItem != null) {
1451                serializer.attribute("", ATTR_BEFORE_MEDIA_ITEM_ID,
1452                                                       beforeMediaItem.getId());
1453            }
1454
1455            if (transition instanceof TransitionSliding) {
1456                serializer.attribute("", ATTR_DIRECTION,
1457                        Integer.toString(((TransitionSliding)transition).getDirection()));
1458            } else if (transition instanceof TransitionAlpha) {
1459                TransitionAlpha ta = (TransitionAlpha)transition;
1460                serializer.attribute("", ATTR_BLENDING,
1461                                     Integer.toString(ta.getBlendingPercent()));
1462                serializer.attribute("", ATTR_INVERT,
1463                                               Boolean.toString(ta.isInvert()));
1464                if (ta.getMaskFilename() != null) {
1465                    serializer.attribute("", ATTR_MASK, ta.getMaskFilename());
1466                }
1467            }
1468            serializer.endTag("", TAG_TRANSITION);
1469        }
1470        serializer.endTag("", TAG_TRANSITIONS);
1471        serializer.startTag("", TAG_AUDIO_TRACKS);
1472        for (AudioTrack at : mAudioTracks) {
1473            serializer.startTag("", TAG_AUDIO_TRACK);
1474            serializer.attribute("", ATTR_ID, at.getId());
1475            serializer.attribute("", ATTR_FILENAME, at.getFilename());
1476            serializer.attribute("", ATTR_START_TIME,
1477                                              Long.toString(at.getStartTime()));
1478            serializer.attribute("", ATTR_BEGIN_TIME,
1479                                      Long.toString(at.getBoundaryBeginTime()));
1480            serializer.attribute("", ATTR_END_TIME,
1481                                        Long.toString(at.getBoundaryEndTime()));
1482            serializer.attribute("", ATTR_VOLUME,
1483                                              Integer.toString(at.getVolume()));
1484            serializer.attribute("", ATTR_DUCK_ENABLED,
1485                                       Boolean.toString(at.isDuckingEnabled()));
1486            serializer.attribute("", ATTR_DUCKED_TRACK_VOLUME,
1487                                   Integer.toString(at.getDuckedTrackVolume()));
1488            serializer.attribute("", ATTR_DUCK_THRESHOLD,
1489                                   Integer.toString(at.getDuckingThreshhold()));
1490            serializer.attribute("", ATTR_MUTED, Boolean.toString(at.isMuted()));
1491            serializer.attribute("", ATTR_LOOP, Boolean.toString(at.isLooping()));
1492            if (at.getAudioWaveformFilename() != null) {
1493                serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
1494                        at.getAudioWaveformFilename());
1495            }
1496
1497            serializer.endTag("", TAG_AUDIO_TRACK);
1498        }
1499        serializer.endTag("", TAG_AUDIO_TRACKS);
1500
1501        serializer.endTag("", TAG_PROJECT);
1502        serializer.endDocument();
1503
1504        /**
1505         *  Save the metadata XML file
1506         */
1507        final FileOutputStream out = new FileOutputStream(new File(getPath(),
1508                                                          PROJECT_FILENAME));
1509        out.write(writer.toString().getBytes());
1510        out.flush();
1511        out.close();
1512    }
1513
1514    /*
1515     * {@inheritDoc}
1516     */
1517    public void setAspectRatio(int aspectRatio) {
1518        mAspectRatio = aspectRatio;
1519        /**
1520         *  Invalidate all transitions
1521         */
1522        mMANativeHelper.setGeneratePreview(true);
1523
1524        for (Transition transition : mTransitions) {
1525            transition.invalidate();
1526        }
1527    }
1528
1529    /*
1530     * {@inheritDoc}
1531     */
1532    public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs,
1533                             boolean loop, int callbackAfterFrameCount,
1534                             PreviewProgressListener listener) {
1535
1536        if (surfaceHolder == null) {
1537            throw new IllegalArgumentException();
1538        }
1539
1540        final Surface surface = surfaceHolder.getSurface();
1541        if (surface == null) {
1542            throw new IllegalArgumentException("Surface could not be retrieved from surface holder");
1543        }
1544
1545        if (listener == null) {
1546            throw new IllegalArgumentException();
1547        }
1548
1549        if (fromMs >= mDurationMs) {
1550            throw new IllegalArgumentException("Requested time not correct");
1551        }
1552
1553        if (fromMs < 0) {
1554            throw new IllegalArgumentException("Requested time not correct");
1555        }
1556
1557        boolean semAcquireDone = false;
1558        try{
1559            mMANativeHelper.lock();
1560            semAcquireDone = true;
1561
1562            if (mMediaItems.size() > 0) {
1563                mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions,
1564                                                  mAudioTracks, null);
1565                mMANativeHelper.doPreview(surface, fromMs, toMs, loop,
1566                                 callbackAfterFrameCount, listener);
1567                mPreviewInProgress = true;
1568            }
1569            /**
1570             *  release on complete by calling stopPreview
1571             */
1572        } catch (InterruptedException  ex) {
1573            Log.e(TAG, "Sem acquire NOT successful in startPreview");
1574        } catch (IllegalArgumentException ex) {
1575            Log.e(TAG, "Illegal Argument exception in do preview");
1576            throw ex;
1577        } catch (IllegalStateException ex) {
1578            Log.e(TAG, "Illegal State exception in do preview");
1579            throw ex;
1580        } catch (RuntimeException ex) {
1581            Log.e(TAG, "Runtime exception in do preview");
1582            throw ex;
1583        } finally {
1584            if (semAcquireDone) {
1585                mMANativeHelper.unlock();
1586            }
1587        }
1588    }
1589
1590    /*
1591     * {@inheritDoc}
1592     */
1593    public long stopPreview() {
1594        if (mPreviewInProgress) {
1595            long result = mMANativeHelper.stopPreview();
1596            mPreviewInProgress = false;
1597            /**
1598             *  release the sem acquired in startPreview
1599             */
1600            mMANativeHelper.unlock();
1601            return result;
1602        } else {
1603            return 0;
1604        }
1605    }
1606
1607    /*
1608     * Remove transitions associated with the specified media item
1609     *
1610     * @param mediaItem The media item
1611     */
1612    private void removeAdjacentTransitions(MediaItem mediaItem) {
1613        final Transition beginTransition = mediaItem.getBeginTransition();
1614        if (beginTransition != null) {
1615            if (beginTransition.getAfterMediaItem() != null) {
1616                beginTransition.getAfterMediaItem().setEndTransition(null);
1617            }
1618            beginTransition.invalidate();
1619            mTransitions.remove(beginTransition);
1620        }
1621
1622        final Transition endTransition = mediaItem.getEndTransition();
1623        if (endTransition != null) {
1624            if (endTransition.getBeforeMediaItem() != null) {
1625                endTransition.getBeforeMediaItem().setBeginTransition(null);
1626            }
1627            endTransition.invalidate();
1628            mTransitions.remove(endTransition);
1629        }
1630
1631        mediaItem.setBeginTransition(null);
1632        mediaItem.setEndTransition(null);
1633    }
1634
1635    /**
1636     * Remove the transition before this media item
1637     *
1638     * @param index The media item index
1639     */
1640    private void removeTransitionBefore(int index) {
1641        final MediaItem mediaItem = mMediaItems.get(index);
1642        final Iterator<Transition> it = mTransitions.iterator();
1643        while (it.hasNext()) {
1644            Transition t = it.next();
1645            if (t.getBeforeMediaItem() == mediaItem) {
1646                mMANativeHelper.setGeneratePreview(true);
1647                it.remove();
1648                t.invalidate();
1649                mediaItem.setBeginTransition(null);
1650                if (index > 0) {
1651                    mMediaItems.get(index - 1).setEndTransition(null);
1652                }
1653                break;
1654            }
1655        }
1656    }
1657
1658    /**
1659     * Remove the transition after this media item
1660     *
1661     * @param mediaItem The media item
1662     */
1663    private void removeTransitionAfter(int index) {
1664        final MediaItem mediaItem = mMediaItems.get(index);
1665        final Iterator<Transition> it = mTransitions.iterator();
1666        while (it.hasNext()) {
1667            Transition t = it.next();
1668            if (t.getAfterMediaItem() == mediaItem) {
1669                mMANativeHelper.setGeneratePreview(true);
1670                it.remove();
1671                t.invalidate();
1672                mediaItem.setEndTransition(null);
1673                /**
1674                 *  Invalidate the reference in the next media item
1675                 */
1676                if (index < mMediaItems.size() - 1) {
1677                    mMediaItems.get(index + 1).setBeginTransition(null);
1678                }
1679                break;
1680            }
1681        }
1682    }
1683
1684    /**
1685     * Compute the duration
1686     */
1687    private void computeTimelineDuration() {
1688        mDurationMs = 0;
1689        final int mediaItemsCount = mMediaItems.size();
1690        for (int i = 0; i < mediaItemsCount; i++) {
1691            final MediaItem mediaItem = mMediaItems.get(i);
1692            mDurationMs += mediaItem.getTimelineDuration();
1693            if (mediaItem.getEndTransition() != null) {
1694                if (i < mediaItemsCount - 1) {
1695                    mDurationMs -= mediaItem.getEndTransition().getDuration();
1696                }
1697            }
1698        }
1699    }
1700
1701    /*
1702     * Generate the project thumbnail
1703     */
1704    private void generateProjectThumbnail() {
1705        /*
1706         * If a thumbnail already exists, then delete it first
1707         */
1708        if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) {
1709            (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete();
1710        }
1711        /*
1712         * Generate a new thumbnail for the project from first media Item
1713         */
1714        if (mMediaItems.size() > 0) {
1715            MediaItem mI = mMediaItems.get(0);
1716            /*
1717             * Lets initialize the width for default aspect ratio i.e 16:9
1718             */
1719            int height = 480;
1720            int width = 854;
1721            switch (mI.getAspectRatio()) {
1722                case MediaProperties.ASPECT_RATIO_3_2:
1723                    width = 720;
1724                    break;
1725                case MediaProperties.ASPECT_RATIO_4_3:
1726                    width = 640;
1727                    break;
1728                case MediaProperties.ASPECT_RATIO_5_3:
1729                    width = 800;
1730                    break;
1731                case MediaProperties.ASPECT_RATIO_11_9:
1732                    width = 586;
1733                    break;
1734                case MediaProperties.ASPECT_RATIO_16_9:
1735                case MediaProperties.ASPECT_RATIO_UNDEFINED:
1736                    break;
1737            }
1738
1739            Bitmap projectBitmap = null;
1740            try {
1741                projectBitmap = mI.getThumbnail(width, height, 500);
1742            } catch (IllegalArgumentException e) {
1743                throw new IllegalArgumentException ("Illegal argument error creating project thumbnail");
1744            } catch (IOException e) {
1745                throw new IllegalArgumentException ("IO Error creating project thumbnail");
1746            }
1747
1748            try {
1749                FileOutputStream stream = new FileOutputStream(mProjectPath + "/"
1750                                                          + THUMBNAIL_FILENAME);
1751                projectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
1752                stream.flush();
1753                stream.close();
1754            } catch (IOException e) {
1755                throw new IllegalArgumentException ("Error creating project thumbnail");
1756            } finally {
1757                projectBitmap.recycle();
1758            }
1759        }
1760    }
1761
1762    /**
1763     * Clears the preview surface
1764     *
1765     * @param surfaceHolder SurfaceHolder where the preview is rendered
1766     * and needs to be cleared.
1767     */
1768    public void clearSurface(SurfaceHolder surfaceHolder) {
1769        if (surfaceHolder == null) {
1770            throw new IllegalArgumentException("Invalid surface holder");
1771        }
1772
1773        final Surface surface = surfaceHolder.getSurface();
1774        if (surface == null) {
1775            throw new IllegalArgumentException("Surface could not be retrieved from surface holder");
1776        }
1777
1778        if (mMANativeHelper != null) {
1779            mMANativeHelper.clearPreviewSurface(surface);
1780        } else {
1781            Log.w(TAG, "Native helper was not ready!");
1782        }
1783    }
1784}
1785